am b8814dce
: Merge "Allow sniffers to return a packet of opaque data that the corresponding extractor can take advantage of to not duplicate work already done sniffing. The mp3 extractor takes advantage of this now." into gingerbread
Merge commit 'b8814dce287552c1bdf13fa999296ebc7387776d' into gingerbread-plus-aosp * commit 'b8814dce287552c1bdf13fa999296ebc7387776d': Allow sniffers to return a packet of opaque data that the corresponding extractor can take advantage of to not duplicate work already done sniffing. The mp3 extractor takes advantage of this now.
This commit is contained in:
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
|
struct AMessage;
|
||||||
class String8;
|
class String8;
|
||||||
|
|
||||||
class DataSource : public RefBase {
|
class DataSource : public RefBase {
|
||||||
@ -59,10 +60,14 @@ public:
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
bool sniff(String8 *mimeType, float *confidence);
|
bool sniff(String8 *mimeType, float *confidence, sp<AMessage> *meta);
|
||||||
|
|
||||||
|
// The sniffer can optionally fill in "meta" with an AMessage containing
|
||||||
|
// a dictionary of values that helps the corresponding extractor initialize
|
||||||
|
// its state without duplicating effort already exerted by the sniffer.
|
||||||
typedef bool (*SnifferFunc)(
|
typedef bool (*SnifferFunc)(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence);
|
const sp<DataSource> &source, String8 *mimeType,
|
||||||
|
float *confidence, sp<AMessage> *meta);
|
||||||
|
|
||||||
static void RegisterSniffer(SnifferFunc func);
|
static void RegisterSniffer(SnifferFunc func);
|
||||||
static void RegisterDefaultSniffers();
|
static void RegisterDefaultSniffers();
|
||||||
|
@ -87,7 +87,7 @@ AMRExtractor::AMRExtractor(const sp<DataSource> &source)
|
|||||||
mInitCheck(NO_INIT) {
|
mInitCheck(NO_INIT) {
|
||||||
String8 mimeType;
|
String8 mimeType;
|
||||||
float confidence;
|
float confidence;
|
||||||
if (!SniffAMR(mDataSource, &mimeType, &confidence)) {
|
if (!SniffAMR(mDataSource, &mimeType, &confidence, NULL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +276,8 @@ status_t AMRSource::read(
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
bool SniffAMR(
|
bool SniffAMR(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *) {
|
||||||
char header[9];
|
char header[9];
|
||||||
|
|
||||||
if (source->readAt(0, header, sizeof(header)) != sizeof(header)) {
|
if (source->readAt(0, header, sizeof(header)) != sizeof(header)) {
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "matroska/MatroskaExtractor.h"
|
#include "matroska/MatroskaExtractor.h"
|
||||||
|
|
||||||
|
#include <media/stagefright/foundation/AMessage.h>
|
||||||
#include <media/stagefright/DataSource.h>
|
#include <media/stagefright/DataSource.h>
|
||||||
#include <media/stagefright/FileSource.h>
|
#include <media/stagefright/FileSource.h>
|
||||||
#include <media/stagefright/MediaErrors.h>
|
#include <media/stagefright/MediaErrors.h>
|
||||||
@ -56,19 +57,23 @@ status_t DataSource::getSize(off_t *size) {
|
|||||||
Mutex DataSource::gSnifferMutex;
|
Mutex DataSource::gSnifferMutex;
|
||||||
List<DataSource::SnifferFunc> DataSource::gSniffers;
|
List<DataSource::SnifferFunc> DataSource::gSniffers;
|
||||||
|
|
||||||
bool DataSource::sniff(String8 *mimeType, float *confidence) {
|
bool DataSource::sniff(
|
||||||
|
String8 *mimeType, float *confidence, sp<AMessage> *meta) {
|
||||||
*mimeType = "";
|
*mimeType = "";
|
||||||
*confidence = 0.0f;
|
*confidence = 0.0f;
|
||||||
|
meta->clear();
|
||||||
|
|
||||||
Mutex::Autolock autoLock(gSnifferMutex);
|
Mutex::Autolock autoLock(gSnifferMutex);
|
||||||
for (List<SnifferFunc>::iterator it = gSniffers.begin();
|
for (List<SnifferFunc>::iterator it = gSniffers.begin();
|
||||||
it != gSniffers.end(); ++it) {
|
it != gSniffers.end(); ++it) {
|
||||||
String8 newMimeType;
|
String8 newMimeType;
|
||||||
float newConfidence;
|
float newConfidence;
|
||||||
if ((*it)(this, &newMimeType, &newConfidence)) {
|
sp<AMessage> newMeta;
|
||||||
|
if ((*it)(this, &newMimeType, &newConfidence, &newMeta)) {
|
||||||
if (newConfidence > *confidence) {
|
if (newConfidence > *confidence) {
|
||||||
*mimeType = newMimeType;
|
*mimeType = newMimeType;
|
||||||
*confidence = newConfidence;
|
*confidence = newConfidence;
|
||||||
|
*meta = newMeta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,13 +97,13 @@ void DataSource::RegisterSniffer(SnifferFunc func) {
|
|||||||
|
|
||||||
// static
|
// static
|
||||||
void DataSource::RegisterDefaultSniffers() {
|
void DataSource::RegisterDefaultSniffers() {
|
||||||
RegisterSniffer(SniffMP3);
|
|
||||||
RegisterSniffer(SniffMPEG4);
|
RegisterSniffer(SniffMPEG4);
|
||||||
RegisterSniffer(SniffAMR);
|
|
||||||
RegisterSniffer(SniffWAV);
|
|
||||||
RegisterSniffer(SniffOgg);
|
|
||||||
RegisterSniffer(SniffMatroska);
|
RegisterSniffer(SniffMatroska);
|
||||||
|
RegisterSniffer(SniffOgg);
|
||||||
|
RegisterSniffer(SniffWAV);
|
||||||
|
RegisterSniffer(SniffAMR);
|
||||||
RegisterSniffer(SniffMPEG2TS);
|
RegisterSniffer(SniffMPEG2TS);
|
||||||
|
RegisterSniffer(SniffMP3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "include/ID3.h"
|
#include "include/ID3.h"
|
||||||
|
|
||||||
|
#include <media/stagefright/foundation/AMessage.h>
|
||||||
#include <media/stagefright/DataSource.h>
|
#include <media/stagefright/DataSource.h>
|
||||||
#include <media/stagefright/MediaBuffer.h>
|
#include <media/stagefright/MediaBuffer.h>
|
||||||
#include <media/stagefright/MediaBufferGroup.h>
|
#include <media/stagefright/MediaBufferGroup.h>
|
||||||
@ -456,15 +457,31 @@ private:
|
|||||||
MP3Source &operator=(const MP3Source &);
|
MP3Source &operator=(const MP3Source &);
|
||||||
};
|
};
|
||||||
|
|
||||||
MP3Extractor::MP3Extractor(const sp<DataSource> &source)
|
MP3Extractor::MP3Extractor(
|
||||||
|
const sp<DataSource> &source, const sp<AMessage> &meta)
|
||||||
: mDataSource(source),
|
: mDataSource(source),
|
||||||
mFirstFramePos(-1),
|
mFirstFramePos(-1),
|
||||||
mFixedHeader(0),
|
mFixedHeader(0),
|
||||||
mByteNumber(0) {
|
mByteNumber(0) {
|
||||||
off_t pos = 0;
|
off_t pos = 0;
|
||||||
uint32_t header;
|
uint32_t header;
|
||||||
bool success = Resync(mDataSource, 0, &pos, &header);
|
bool success;
|
||||||
CHECK(success);
|
|
||||||
|
int64_t meta_offset;
|
||||||
|
uint32_t meta_header;
|
||||||
|
if (meta != NULL
|
||||||
|
&& meta->findInt64("offset", &meta_offset)
|
||||||
|
&& meta->findInt32("header", (int32_t *)&meta_header)) {
|
||||||
|
// The sniffer has already done all the hard work for us, simply
|
||||||
|
// accept its judgement.
|
||||||
|
pos = (off_t)meta_offset;
|
||||||
|
header = meta_header;
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
} else {
|
||||||
|
success = Resync(mDataSource, 0, &pos, &header);
|
||||||
|
CHECK(success);
|
||||||
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
mFirstFramePos = pos;
|
mFirstFramePos = pos;
|
||||||
@ -759,15 +776,20 @@ sp<MetaData> MP3Extractor::getMetaData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SniffMP3(
|
bool SniffMP3(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
|
const sp<DataSource> &source, String8 *mimeType,
|
||||||
|
float *confidence, sp<AMessage> *meta) {
|
||||||
off_t pos = 0;
|
off_t pos = 0;
|
||||||
uint32_t header;
|
uint32_t header;
|
||||||
if (!Resync(source, 0, &pos, &header)) {
|
if (!Resync(source, 0, &pos, &header)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*meta = new AMessage;
|
||||||
|
(*meta)->setInt64("offset", pos);
|
||||||
|
(*meta)->setInt32("header", header);
|
||||||
|
|
||||||
*mimeType = MEDIA_MIMETYPE_AUDIO_MPEG;
|
*mimeType = MEDIA_MIMETYPE_AUDIO_MPEG;
|
||||||
*confidence = 0.3f;
|
*confidence = 0.2f;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1738,7 +1738,7 @@ static bool LegacySniffMPEG4(
|
|||||||
|| !memcmp(header, "ftypM4A ", 8) || !memcmp(header, "ftypf4v ", 8)
|
|| !memcmp(header, "ftypM4A ", 8) || !memcmp(header, "ftypf4v ", 8)
|
||||||
|| !memcmp(header, "ftypkddi", 8) || !memcmp(header, "ftypM4VP", 8)) {
|
|| !memcmp(header, "ftypkddi", 8) || !memcmp(header, "ftypM4VP", 8)) {
|
||||||
*mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
|
*mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
|
||||||
*confidence = 0.1;
|
*confidence = 0.4;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1805,13 +1805,14 @@ static bool BetterSniffMPEG4(
|
|||||||
}
|
}
|
||||||
|
|
||||||
*mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
|
*mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
|
||||||
*confidence = 0.3f;
|
*confidence = 0.4f;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SniffMPEG4(
|
bool SniffMPEG4(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *) {
|
||||||
if (BetterSniffMPEG4(source, mimeType, confidence)) {
|
if (BetterSniffMPEG4(source, mimeType, confidence)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include "matroska/MatroskaExtractor.h"
|
#include "matroska/MatroskaExtractor.h"
|
||||||
|
|
||||||
|
#include <media/stagefright/foundation/AMessage.h>
|
||||||
#include <media/stagefright/DataSource.h>
|
#include <media/stagefright/DataSource.h>
|
||||||
#include <media/stagefright/MediaDefs.h>
|
#include <media/stagefright/MediaDefs.h>
|
||||||
#include <media/stagefright/MediaExtractor.h>
|
#include <media/stagefright/MediaExtractor.h>
|
||||||
@ -46,10 +47,12 @@ uint32_t MediaExtractor::flags() const {
|
|||||||
// static
|
// static
|
||||||
sp<MediaExtractor> MediaExtractor::Create(
|
sp<MediaExtractor> MediaExtractor::Create(
|
||||||
const sp<DataSource> &source, const char *mime) {
|
const sp<DataSource> &source, const char *mime) {
|
||||||
|
sp<AMessage> meta;
|
||||||
|
|
||||||
String8 tmp;
|
String8 tmp;
|
||||||
if (mime == NULL) {
|
if (mime == NULL) {
|
||||||
float confidence;
|
float confidence;
|
||||||
if (!source->sniff(&tmp, &confidence)) {
|
if (!source->sniff(&tmp, &confidence, &meta)) {
|
||||||
LOGV("FAILED to autodetect media content.");
|
LOGV("FAILED to autodetect media content.");
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -64,7 +67,7 @@ sp<MediaExtractor> MediaExtractor::Create(
|
|||||||
|| !strcasecmp(mime, "audio/mp4")) {
|
|| !strcasecmp(mime, "audio/mp4")) {
|
||||||
return new MPEG4Extractor(source);
|
return new MPEG4Extractor(source);
|
||||||
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
|
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
|
||||||
return new MP3Extractor(source);
|
return new MP3Extractor(source, meta);
|
||||||
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
|
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
|
||||||
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
|
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
|
||||||
return new AMRExtractor(source);
|
return new AMRExtractor(source);
|
||||||
|
@ -804,7 +804,8 @@ sp<MetaData> OggExtractor::getMetaData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SniffOgg(
|
bool SniffOgg(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *) {
|
||||||
char tmp[4];
|
char tmp[4];
|
||||||
if (source->readAt(0, tmp, 4) < 4 || memcmp(tmp, "OggS", 4)) {
|
if (source->readAt(0, tmp, 4) < 4 || memcmp(tmp, "OggS", 4)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -404,7 +404,8 @@ status_t WAVSource::read(
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
bool SniffWAV(
|
bool SniffWAV(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *) {
|
||||||
char header[12];
|
char header[12];
|
||||||
if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
|
if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
|
struct AMessage;
|
||||||
class String8;
|
class String8;
|
||||||
|
|
||||||
class AMRExtractor : public MediaExtractor {
|
class AMRExtractor : public MediaExtractor {
|
||||||
@ -49,7 +50,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool SniffAMR(
|
bool SniffAMR(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence);
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *);
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
||||||
|
@ -22,13 +22,14 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
|
struct AMessage;
|
||||||
class DataSource;
|
class DataSource;
|
||||||
class String8;
|
class String8;
|
||||||
|
|
||||||
class MP3Extractor : public MediaExtractor {
|
class MP3Extractor : public MediaExtractor {
|
||||||
public:
|
public:
|
||||||
// Extractor assumes ownership of "source".
|
// Extractor assumes ownership of "source".
|
||||||
MP3Extractor(const sp<DataSource> &source);
|
MP3Extractor(const sp<DataSource> &source, const sp<AMessage> &meta);
|
||||||
|
|
||||||
virtual size_t countTracks();
|
virtual size_t countTracks();
|
||||||
virtual sp<MediaSource> getTrack(size_t index);
|
virtual sp<MediaSource> getTrack(size_t index);
|
||||||
@ -52,7 +53,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool SniffMP3(
|
bool SniffMP3(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence);
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *meta);
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
|
struct AMessage;
|
||||||
struct AnotherPacketSource;
|
struct AnotherPacketSource;
|
||||||
struct ATSParser;
|
struct ATSParser;
|
||||||
struct DataSource;
|
struct DataSource;
|
||||||
@ -47,7 +48,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool SniffMPEG2TS(
|
bool SniffMPEG2TS(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence);
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *);
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
|
struct AMessage;
|
||||||
class DataSource;
|
class DataSource;
|
||||||
class SampleTable;
|
class SampleTable;
|
||||||
class String8;
|
class String8;
|
||||||
@ -75,7 +76,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool SniffMPEG4(
|
bool SniffMPEG4(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence);
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *);
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
|
struct AMessage;
|
||||||
class DataSource;
|
class DataSource;
|
||||||
class String8;
|
class String8;
|
||||||
|
|
||||||
@ -53,7 +54,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool SniffOgg(
|
bool SniffOgg(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence);
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *);
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
|
struct AMessage;
|
||||||
class DataSource;
|
class DataSource;
|
||||||
class String8;
|
class String8;
|
||||||
|
|
||||||
@ -58,7 +59,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool SniffWAV(
|
bool SniffWAV(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence);
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *);
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
||||||
|
@ -579,7 +579,8 @@ sp<MetaData> MatroskaExtractor::getMetaData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SniffMatroska(
|
bool SniffMatroska(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *) {
|
||||||
DataSourceReader reader(source);
|
DataSourceReader reader(source);
|
||||||
mkvparser::EBMLHeader ebmlHeader;
|
mkvparser::EBMLHeader ebmlHeader;
|
||||||
long long pos;
|
long long pos;
|
||||||
|
@ -27,6 +27,7 @@ struct Segment;
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
|
struct AMessage;
|
||||||
class String8;
|
class String8;
|
||||||
|
|
||||||
struct DataSourceReader;
|
struct DataSourceReader;
|
||||||
@ -69,7 +70,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool SniffMatroska(
|
bool SniffMatroska(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence);
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *);
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
||||||
|
@ -174,7 +174,8 @@ status_t MPEG2TSExtractor::feedMore() {
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
bool SniffMPEG2TS(
|
bool SniffMPEG2TS(
|
||||||
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
|
const sp<DataSource> &source, String8 *mimeType, float *confidence,
|
||||||
|
sp<AMessage> *) {
|
||||||
#if 0
|
#if 0
|
||||||
char header;
|
char header;
|
||||||
if (source->readAt(0, &header, 1) != 1 || header != 0x47) {
|
if (source->readAt(0, &header, 1) != 1 || header != 0x47) {
|
||||||
|
Reference in New Issue
Block a user