mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-26 23:23:33 +00:00
Bug 778053 - Read wave metadata. r=kinetik
This commit is contained in:
parent
9ae048d084
commit
b1e3524e71
@ -12,6 +12,8 @@
|
||||
#include "VideoUtils.h"
|
||||
|
||||
#include "mozilla/StandardInteger.h"
|
||||
#include "mozilla/Util.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -31,17 +33,24 @@ extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
#define SEEK_LOG(type, msg)
|
||||
#endif
|
||||
|
||||
struct waveIdToName {
|
||||
uint32_t id;
|
||||
nsCString name;
|
||||
};
|
||||
|
||||
|
||||
// Magic values that identify RIFF chunks we're interested in.
|
||||
static const uint32_t RIFF_CHUNK_MAGIC = 0x52494646;
|
||||
static const uint32_t WAVE_CHUNK_MAGIC = 0x57415645;
|
||||
static const uint32_t FRMT_CHUNK_MAGIC = 0x666d7420;
|
||||
static const uint32_t DATA_CHUNK_MAGIC = 0x64617461;
|
||||
static const uint32_t LIST_CHUNK_MAGIC = 0x4c495354;
|
||||
|
||||
// Size of RIFF chunk header. 4 byte chunk header type and 4 byte size field.
|
||||
static const uint16_t RIFF_CHUNK_HEADER_SIZE = 8;
|
||||
// Size of chunk header. 4 byte chunk header type and 4 byte size field.
|
||||
static const uint16_t CHUNK_HEADER_SIZE = 8;
|
||||
|
||||
// Size of RIFF header. RIFF chunk and 4 byte RIFF type.
|
||||
static const uint16_t RIFF_INITIAL_SIZE = RIFF_CHUNK_HEADER_SIZE + 4;
|
||||
static const uint16_t RIFF_INITIAL_SIZE = CHUNK_HEADER_SIZE + 4;
|
||||
|
||||
// Size of required part of format chunk. Actual format chunks may be
|
||||
// extended (for non-PCM encodings), but we skip any extended data.
|
||||
@ -125,11 +134,18 @@ nsresult WaveReader::ReadMetadata(VideoInfo* aInfo,
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
bool loaded = LoadRIFFChunk() && LoadFormatChunk() && FindDataOffset();
|
||||
bool loaded = LoadRIFFChunk();
|
||||
if (!loaded) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsAutoPtr<nsHTMLMediaElement::MetadataTags> tags;
|
||||
|
||||
bool loadAllChunks = LoadAllChunks(tags);
|
||||
if (!loadAllChunks) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mInfo.mHasAudio = true;
|
||||
mInfo.mHasVideo = false;
|
||||
mInfo.mAudioRate = mSampleRate;
|
||||
@ -137,7 +153,7 @@ nsresult WaveReader::ReadMetadata(VideoInfo* aInfo,
|
||||
|
||||
*aInfo = mInfo;
|
||||
|
||||
*aTags = nullptr;
|
||||
*aTags = tags.forget();
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
|
||||
@ -335,49 +351,9 @@ WaveReader::LoadRIFFChunk()
|
||||
}
|
||||
|
||||
bool
|
||||
WaveReader::ScanForwardUntil(uint32_t aWantedChunk, uint32_t* aChunkSize)
|
||||
WaveReader::LoadFormatChunk(uint32_t aChunkSize)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(aChunkSize, "Require aChunkSize argument");
|
||||
*aChunkSize = 0;
|
||||
|
||||
for (;;) {
|
||||
static const unsigned int CHUNK_HEADER_SIZE = 8;
|
||||
char chunkHeader[CHUNK_HEADER_SIZE];
|
||||
const char* p = chunkHeader;
|
||||
|
||||
if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PR_STATIC_ASSERT(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE);
|
||||
uint32_t magic = ReadUint32BE(&p);
|
||||
uint32_t chunkSize = ReadUint32LE(&p);
|
||||
|
||||
if (magic == aWantedChunk) {
|
||||
*aChunkSize = chunkSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
// RIFF chunks are two-byte aligned, so round up if necessary.
|
||||
chunkSize += chunkSize % 2;
|
||||
|
||||
static const unsigned int MAX_CHUNK_SIZE = 1 << 16;
|
||||
PR_STATIC_ASSERT(MAX_CHUNK_SIZE < UINT_MAX / sizeof(char));
|
||||
nsAutoArrayPtr<char> chunk(new char[MAX_CHUNK_SIZE]);
|
||||
while (chunkSize > 0) {
|
||||
uint32_t size = NS_MIN(chunkSize, MAX_CHUNK_SIZE);
|
||||
if (!ReadAll(chunk.get(), size)) {
|
||||
return false;
|
||||
}
|
||||
chunkSize -= size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
WaveReader::LoadFormatChunk()
|
||||
{
|
||||
uint32_t fmtSize, rate, channels, frameSize, sampleFormat;
|
||||
uint32_t rate, channels, frameSize, sampleFormat;
|
||||
char waveFormat[WAVE_FORMAT_CHUNK_SIZE];
|
||||
const char* p = waveFormat;
|
||||
|
||||
@ -385,12 +361,6 @@ WaveReader::LoadFormatChunk()
|
||||
NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
"LoadFormatChunk called with unaligned resource");
|
||||
|
||||
// The "format" chunk may not directly follow the "riff" chunk, so skip
|
||||
// over any intermediate chunks.
|
||||
if (!ScanForwardUntil(FRMT_CHUNK_MAGIC, &fmtSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadAll(waveFormat, sizeof(waveFormat))) {
|
||||
return false;
|
||||
}
|
||||
@ -421,7 +391,7 @@ WaveReader::LoadFormatChunk()
|
||||
// extension size of 0 bytes. Be polite and handle this rather than
|
||||
// considering the file invalid. This code skips any extension of the
|
||||
// "format" chunk.
|
||||
if (fmtSize > WAVE_FORMAT_CHUNK_SIZE) {
|
||||
if (aChunkSize > WAVE_FORMAT_CHUNK_SIZE) {
|
||||
char extLength[2];
|
||||
const char* p = extLength;
|
||||
|
||||
@ -431,7 +401,7 @@ WaveReader::LoadFormatChunk()
|
||||
|
||||
PR_STATIC_ASSERT(sizeof(uint16_t) <= sizeof(extLength));
|
||||
uint16_t extra = ReadUint16LE(&p);
|
||||
if (fmtSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
|
||||
if (aChunkSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
|
||||
NS_WARNING("Invalid extended format chunk size");
|
||||
return false;
|
||||
}
|
||||
@ -476,19 +446,12 @@ WaveReader::LoadFormatChunk()
|
||||
}
|
||||
|
||||
bool
|
||||
WaveReader::FindDataOffset()
|
||||
WaveReader::FindDataOffset(uint32_t aChunkSize)
|
||||
{
|
||||
// RIFF chunks are always word (two byte) aligned.
|
||||
NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
"FindDataOffset called with unaligned resource");
|
||||
|
||||
// The "data" chunk may not directly follow the "format" chunk, so skip
|
||||
// over any intermediate chunks.
|
||||
uint32_t length;
|
||||
if (!ScanForwardUntil(DATA_CHUNK_MAGIC, &length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t offset = mDecoder->GetResource()->Tell();
|
||||
if (offset <= 0 || offset > UINT32_MAX) {
|
||||
NS_WARNING("PCM data offset out of range");
|
||||
@ -496,7 +459,7 @@ WaveReader::FindDataOffset()
|
||||
}
|
||||
|
||||
ReentrantMonitorAutoEnter monitor(mDecoder->GetReentrantMonitor());
|
||||
mWaveLength = length;
|
||||
mWaveLength = aChunkSize;
|
||||
mWavePCMOffset = uint32_t(offset);
|
||||
return true;
|
||||
}
|
||||
@ -543,4 +506,173 @@ WaveReader::GetPosition()
|
||||
return mDecoder->GetResource()->Tell();
|
||||
}
|
||||
|
||||
bool
|
||||
WaveReader::GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(aChunk, "Must have aChunk");
|
||||
NS_ABORT_IF_FALSE(aChunkSize, "Must have aChunkSize");
|
||||
NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
"GetNextChunk called with unaligned resource");
|
||||
|
||||
char chunkHeader[CHUNK_HEADER_SIZE];
|
||||
const char* p = chunkHeader;
|
||||
|
||||
if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PR_STATIC_ASSERT(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE);
|
||||
*aChunk = ReadUint32BE(&p);
|
||||
*aChunkSize = ReadUint32LE(&p);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
WaveReader::LoadListChunk(uint32_t aChunkSize,
|
||||
nsAutoPtr<nsHTMLMediaElement::MetadataTags> &aTags)
|
||||
{
|
||||
// List chunks are always word (two byte) aligned.
|
||||
NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
"LoadListChunk called with unaligned resource");
|
||||
|
||||
static const unsigned int MAX_CHUNK_SIZE = 1 << 16;
|
||||
PR_STATIC_ASSERT(MAX_CHUNK_SIZE < UINT_MAX / sizeof(char));
|
||||
|
||||
if (aChunkSize > MAX_CHUNK_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoArrayPtr<char> chunk(new char[aChunkSize]);
|
||||
if (!ReadAll(chunk.get(), aChunkSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const uint32_t INFO_LIST_MAGIC = 0x494e464f;
|
||||
const char *p = chunk.get();
|
||||
if (ReadUint32BE(&p) != INFO_LIST_MAGIC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const waveIdToName ID_TO_NAME[] = {
|
||||
{ 0x49415254, NS_LITERAL_CSTRING("artist") }, // IART
|
||||
{ 0x49434d54, NS_LITERAL_CSTRING("comments") }, // ICMT
|
||||
{ 0x49474e52, NS_LITERAL_CSTRING("genre") }, // IGNR
|
||||
{ 0x494e414d, NS_LITERAL_CSTRING("name") }, // INAM
|
||||
};
|
||||
|
||||
const char* const end = chunk.get() + aChunkSize;
|
||||
|
||||
aTags = new nsHTMLMediaElement::MetadataTags;
|
||||
aTags->Init();
|
||||
|
||||
while (p + 8 < end) {
|
||||
uint32_t id = ReadUint32BE(&p);
|
||||
// Uppercase tag id, inspired by GStreamer's Wave parser.
|
||||
id &= 0xDFDFDFDF;
|
||||
|
||||
uint32_t length = ReadUint32LE(&p);
|
||||
|
||||
// Subchunk shall not exceed parent chunk.
|
||||
if (p + length > end) {
|
||||
break;
|
||||
}
|
||||
|
||||
nsCString val(p, length);
|
||||
if (val[length - 1] == '\0') {
|
||||
val.SetLength(length - 1);
|
||||
}
|
||||
|
||||
// Chunks in List::INFO are always word (two byte) aligned. So round up if
|
||||
// necessary.
|
||||
length += length % 2;
|
||||
p += length;
|
||||
|
||||
if (!IsUTF8(val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mozilla::ArrayLength(ID_TO_NAME); ++i) {
|
||||
if (id == ID_TO_NAME[i].id) {
|
||||
aTags->Put(ID_TO_NAME[i].name, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
WaveReader::LoadAllChunks(nsAutoPtr<nsHTMLMediaElement::MetadataTags> &aTags)
|
||||
{
|
||||
// Chunks are always word (two byte) aligned.
|
||||
NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
"LoadAllChunks called with unaligned resource");
|
||||
|
||||
bool loadFormatChunk = false;
|
||||
bool findDataOffset = false;
|
||||
|
||||
for (;;) {
|
||||
static const unsigned int CHUNK_HEADER_SIZE = 8;
|
||||
char chunkHeader[CHUNK_HEADER_SIZE];
|
||||
const char* p = chunkHeader;
|
||||
|
||||
if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PR_STATIC_ASSERT(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE);
|
||||
|
||||
uint32_t magic = ReadUint32BE(&p);
|
||||
uint32_t chunkSize = ReadUint32LE(&p);
|
||||
int64_t chunkStart = GetPosition();
|
||||
|
||||
switch (magic) {
|
||||
case FRMT_CHUNK_MAGIC:
|
||||
loadFormatChunk = LoadFormatChunk(chunkSize);
|
||||
if (!loadFormatChunk) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case LIST_CHUNK_MAGIC:
|
||||
if (!aTags) {
|
||||
LoadListChunk(chunkSize, aTags);
|
||||
}
|
||||
break;
|
||||
|
||||
case DATA_CHUNK_MAGIC:
|
||||
findDataOffset = FindDataOffset(chunkSize);
|
||||
return loadFormatChunk && findDataOffset;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// RIFF chunks are two-byte aligned, so round up if necessary.
|
||||
chunkSize += chunkSize % 2;
|
||||
|
||||
// Move forward to next chunk
|
||||
CheckedInt64 forward = CheckedInt64(chunkStart) + chunkSize - GetPosition();
|
||||
|
||||
if (!forward.isValid() || forward.value() < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const int64_t MAX_CHUNK_SIZE = 1 << 16;
|
||||
PR_STATIC_ASSERT(uint64_t(MAX_CHUNK_SIZE) < UINT_MAX / sizeof(char));
|
||||
nsAutoArrayPtr<char> chunk(new char[MAX_CHUNK_SIZE]);
|
||||
while (forward.value() > 0) {
|
||||
int64_t size = NS_MIN(forward.value(), MAX_CHUNK_SIZE);
|
||||
if (!ReadAll(chunk.get(), size)) {
|
||||
return false;
|
||||
}
|
||||
forward -= size;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -46,9 +46,11 @@ public:
|
||||
private:
|
||||
bool ReadAll(char* aBuf, int64_t aSize, int64_t* aBytesRead = nullptr);
|
||||
bool LoadRIFFChunk();
|
||||
bool ScanForwardUntil(uint32_t aWantedChunk, uint32_t* aChunkSize);
|
||||
bool LoadFormatChunk();
|
||||
bool FindDataOffset();
|
||||
bool GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize);
|
||||
bool LoadFormatChunk(uint32_t aChunkSize);
|
||||
bool FindDataOffset(uint32_t aChunkSize);
|
||||
bool LoadListChunk(uint32_t aChunkSize, nsAutoPtr<nsHTMLMediaElement::MetadataTags> &aTags);
|
||||
bool LoadAllChunks(nsAutoPtr<nsHTMLMediaElement::MetadataTags> &aTags);
|
||||
|
||||
// Returns the number of seconds that aBytes represents based on the
|
||||
// current audio parameters. e.g. 176400 bytes is 1 second at 16-bit
|
||||
|
Loading…
x
Reference in New Issue
Block a user