Add support for Opus specific box in iso-bmff

This is part of the effort to support Opus in iso-bmff #83.

Change-Id: Ib3678b9cb74eac76372ed83ad48ce1f203ba0c35
This commit is contained in:
Kongqun Yang 2016-05-09 11:55:14 -07:00 committed by KongQun Yang
parent 26cb91e29b
commit cf4a2447c1
8 changed files with 123 additions and 10 deletions

View File

@ -14,6 +14,8 @@ enum FourCC : uint32_t {
FOURCC_NULL = 0, FOURCC_NULL = 0,
FOURCC_ID32 = 0x49443332, FOURCC_ID32 = 0x49443332,
FOURCC_Head = 0x48656164,
FOURCC_Opus = 0x4f707573,
FOURCC_PRIV = 0x50524956, FOURCC_PRIV = 0x50524956,
FOURCC_aacd = 0x61616364, FOURCC_aacd = 0x61616364,
@ -29,6 +31,7 @@ enum FourCC : uint32_t {
FOURCC_co64 = 0x636f3634, FOURCC_co64 = 0x636f3634,
FOURCC_ctim = 0x6374696d, FOURCC_ctim = 0x6374696d,
FOURCC_ctts = 0x63747473, FOURCC_ctts = 0x63747473,
FOURCC_dOps = 0x644f7073,
FOURCC_dac3 = 0x64616333, FOURCC_dac3 = 0x64616333,
FOURCC_dash = 0x64617368, FOURCC_dash = 0x64617368,
FOURCC_ddts = 0x64647473, FOURCC_ddts = 0x64647473,

View File

@ -1467,6 +1467,57 @@ uint32_t EC3Specific::ComputeSizeInternal() {
return HeaderSize() + data.size(); return HeaderSize() + data.size();
} }
OpusSpecific::OpusSpecific() : preskip(0) {}
OpusSpecific::~OpusSpecific() {}
FourCC OpusSpecific::BoxType() const { return FOURCC_dOps; }
bool OpusSpecific::ReadWriteInternal(BoxBuffer* buffer) {
RCHECK(ReadWriteHeaderInternal(buffer));
if (buffer->Reading()) {
std::vector<uint8_t> data;
const int kMinOpusSpecificBoxDataSize = 11;
RCHECK(buffer->BytesLeft() >= kMinOpusSpecificBoxDataSize);
RCHECK(buffer->ReadWriteVector(&data, buffer->BytesLeft()));
preskip = data[2] + (data[3] << 8);
// https://tools.ietf.org/html/draft-ietf-codec-oggopus-06#section-5
BufferWriter writer;
writer.AppendInt(FOURCC_Opus);
writer.AppendInt(FOURCC_Head);
// The version must always be 1.
const uint8_t kOpusIdentificationHeaderVersion = 1;
data[0] = kOpusIdentificationHeaderVersion;
writer.AppendVector(data);
writer.SwapBuffer(&opus_identification_header);
} else {
// https://tools.ietf.org/html/draft-ietf-codec-oggopus-06#section-5
// The first 8 bytes is "magic signature".
const size_t kOpusMagicSignatureSize = 8u;
DCHECK_GT(opus_identification_header.size(), kOpusMagicSignatureSize);
// https://www.opus-codec.org/docs/opus_in_isobmff.html
// The version field shall be set to 0.
const uint8_t kOpusSpecificBoxVersion = 0;
buffer->writer()->AppendInt(kOpusSpecificBoxVersion);
buffer->writer()->AppendArray(
&opus_identification_header[kOpusMagicSignatureSize + 1],
opus_identification_header.size() - kOpusMagicSignatureSize - 1);
}
return true;
}
uint32_t OpusSpecific::ComputeSizeInternal() {
// This box is optional. Skip it if not initialized.
if (opus_identification_header.empty())
return 0;
// https://tools.ietf.org/html/draft-ietf-codec-oggopus-06#section-5
// The first 8 bytes is "magic signature".
const size_t kOpusMagicSignatureSize = 8u;
DCHECK_GT(opus_identification_header.size(), kOpusMagicSignatureSize);
return HeaderSize() + opus_identification_header.size() -
kOpusMagicSignatureSize;
}
AudioSampleEntry::AudioSampleEntry() AudioSampleEntry::AudioSampleEntry()
: format(FOURCC_NULL), : format(FOURCC_NULL),
data_reference_index(1), data_reference_index(1),
@ -1512,6 +1563,7 @@ bool AudioSampleEntry::ReadWriteInternal(BoxBuffer* buffer) {
RCHECK(buffer->TryReadWriteChild(&ddts)); RCHECK(buffer->TryReadWriteChild(&ddts));
RCHECK(buffer->TryReadWriteChild(&dac3)); RCHECK(buffer->TryReadWriteChild(&dac3));
RCHECK(buffer->TryReadWriteChild(&dec3)); RCHECK(buffer->TryReadWriteChild(&dec3));
RCHECK(buffer->TryReadWriteChild(&dops));
return true; return true;
} }
@ -1519,7 +1571,7 @@ uint32_t AudioSampleEntry::ComputeSizeInternal() {
return HeaderSize() + sizeof(data_reference_index) + sizeof(channelcount) + return HeaderSize() + sizeof(data_reference_index) + sizeof(channelcount) +
sizeof(samplesize) + sizeof(samplerate) + sinf.ComputeSize() + sizeof(samplesize) + sizeof(samplerate) + sinf.ComputeSize() +
esds.ComputeSize() + ddts.ComputeSize() + dac3.ComputeSize() + esds.ComputeSize() + ddts.ComputeSize() + dac3.ComputeSize() +
dec3.ComputeSize() + dec3.ComputeSize() + dops.ComputeSize() +
6 + 8 + // 6 + 8 bytes reserved. 6 + 8 + // 6 + 8 bytes reserved.
4; // 4 bytes predefined. 4; // 4 bytes predefined.
} }

View File

@ -326,6 +326,14 @@ struct EC3Specific : Box {
std::vector<uint8_t> data; std::vector<uint8_t> data;
}; };
struct OpusSpecific : Box {
DECLARE_BOX_METHODS(OpusSpecific);
std::vector<uint8_t> opus_identification_header;
// The number of priming samples. Extracted from |opus_identification_header|.
uint16_t preskip;
};
struct AudioSampleEntry : Box { struct AudioSampleEntry : Box {
DECLARE_BOX_METHODS(AudioSampleEntry); DECLARE_BOX_METHODS(AudioSampleEntry);
// Returns actual format of this sample entry. // Returns actual format of this sample entry.
@ -345,6 +353,7 @@ struct AudioSampleEntry : Box {
DTSSpecific ddts; DTSSpecific ddts;
AC3Specific dac3; AC3Specific dac3;
EC3Specific dec3; EC3Specific dec3;
OpusSpecific dops;
}; };
struct WebVTTConfigurationBox : Box { struct WebVTTConfigurationBox : Box {

View File

@ -234,8 +234,7 @@ inline bool operator==(const ElementaryStreamDescriptor& lhs,
return lhs.es_descriptor == rhs.es_descriptor; return lhs.es_descriptor == rhs.es_descriptor;
} }
inline bool operator==(const DTSSpecific& lhs, inline bool operator==(const DTSSpecific& lhs, const DTSSpecific& rhs) {
const DTSSpecific& rhs) {
return lhs.sampling_frequency == rhs.sampling_frequency && return lhs.sampling_frequency == rhs.sampling_frequency &&
lhs.max_bitrate == rhs.max_bitrate && lhs.max_bitrate == rhs.max_bitrate &&
lhs.avg_bitrate == rhs.avg_bitrate && lhs.avg_bitrate == rhs.avg_bitrate &&
@ -243,16 +242,19 @@ inline bool operator==(const DTSSpecific& lhs,
lhs.extra_data == rhs.extra_data; lhs.extra_data == rhs.extra_data;
} }
inline bool operator==(const AC3Specific& lhs, inline bool operator==(const AC3Specific& lhs, const AC3Specific& rhs) {
const AC3Specific& rhs) {
return lhs.data == rhs.data; return lhs.data == rhs.data;
} }
inline bool operator==(const EC3Specific& lhs, inline bool operator==(const EC3Specific& lhs, const EC3Specific& rhs) {
const EC3Specific& rhs) {
return lhs.data == rhs.data; return lhs.data == rhs.data;
} }
inline bool operator==(const OpusSpecific& lhs, const OpusSpecific& rhs) {
return lhs.opus_identification_header == rhs.opus_identification_header &&
lhs.preskip == rhs.preskip;
}
inline bool operator==(const AudioSampleEntry& lhs, inline bool operator==(const AudioSampleEntry& lhs,
const AudioSampleEntry& rhs) { const AudioSampleEntry& rhs) {
return lhs.format == rhs.format && return lhs.format == rhs.format &&
@ -260,7 +262,7 @@ inline bool operator==(const AudioSampleEntry& lhs,
lhs.channelcount == rhs.channelcount && lhs.channelcount == rhs.channelcount &&
lhs.samplesize == rhs.samplesize && lhs.samplerate == rhs.samplerate && lhs.samplesize == rhs.samplesize && lhs.samplerate == rhs.samplerate &&
lhs.sinf == rhs.sinf && lhs.esds == rhs.esds && lhs.ddts == rhs.ddts && lhs.sinf == rhs.sinf && lhs.esds == rhs.esds && lhs.ddts == rhs.ddts &&
lhs.dac3 == rhs.dac3 && lhs.dec3 == rhs.dec3; lhs.dac3 == rhs.dac3 && lhs.dec3 == rhs.dec3 && lhs.dops == rhs.dops;
} }
inline bool operator==(const WebVTTConfigurationBox& lhs, inline bool operator==(const WebVTTConfigurationBox& lhs,

View File

@ -417,6 +417,21 @@ class BoxDefinitionsTestGeneral : public testing::Test {
dec3->data.assign(kEc3Data, kEc3Data + arraysize(kEc3Data)); dec3->data.assign(kEc3Data, kEc3Data + arraysize(kEc3Data));
} }
void Fill(OpusSpecific* dops) {
const uint8_t kOpusIdentificationHeader[] = {
0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x00};
dops->opus_identification_header.assign(
kOpusIdentificationHeader,
kOpusIdentificationHeader + arraysize(kOpusIdentificationHeader));
dops->preskip = 0x0403;
}
void Modify(OpusSpecific* dops) {
dops->opus_identification_header.resize(
dops->opus_identification_header.size() - 1);
}
void Fill(AudioSampleEntry* enca) { void Fill(AudioSampleEntry* enca) {
enca->format = FOURCC_enca; enca->format = FOURCC_enca;
enca->data_reference_index = 2; enca->data_reference_index = 2;
@ -945,6 +960,7 @@ class BoxDefinitionsTestGeneral : public testing::Test {
bool IsOptional(const CueTimeBox* box) { return true; } bool IsOptional(const CueTimeBox* box) { return true; }
bool IsOptional(const CueSettingsBox* box) { return true; } bool IsOptional(const CueSettingsBox* box) { return true; }
bool IsOptional(const DTSSpecific* box) {return true; } bool IsOptional(const DTSSpecific* box) {return true; }
bool IsOptional(const OpusSpecific* box) {return true; }
protected: protected:
scoped_ptr<BufferWriter> buffer_; scoped_ptr<BufferWriter> buffer_;
@ -976,6 +992,7 @@ typedef testing::Types<FileType,
DTSSpecific, DTSSpecific,
AC3Specific, AC3Specific,
EC3Specific, EC3Specific,
OpusSpecific,
AudioSampleEntry, AudioSampleEntry,
WebVTTConfigurationBox, WebVTTConfigurationBox,
WebVTTSourceLabelBox, WebVTTSourceLabelBox,
@ -1151,6 +1168,21 @@ TEST_F(BoxDefinitionsTest, EC3SampleEntry) {
ASSERT_EQ(entry, entry_readback); ASSERT_EQ(entry, entry_readback);
} }
TEST_F(BoxDefinitionsTest, OpusSampleEntry) {
AudioSampleEntry entry;
entry.format = FOURCC_Opus;
entry.data_reference_index = 2;
entry.channelcount = 2;
entry.samplesize = 16;
entry.samplerate = 48000;
Fill(&entry.dops);
entry.Write(this->buffer_.get());
AudioSampleEntry entry_readback;
ASSERT_TRUE(ReadBack(&entry_readback));
ASSERT_EQ(entry, entry_readback);
}
TEST_F(BoxDefinitionsTest, CompactSampleSize_FieldSize16) { TEST_F(BoxDefinitionsTest, CompactSampleSize_FieldSize16) {
CompactSampleSize stz2; CompactSampleSize stz2;
stz2.field_size = 16; stz2.field_size = 16;

View File

@ -84,6 +84,7 @@ AudioCodec FourCCToAudioCodec(FourCC fourcc) {
// Default DTS audio number of channels for 5.1 channel layout. // Default DTS audio number of channels for 5.1 channel layout.
const uint8_t kDtsAudioNumChannels = 6; const uint8_t kDtsAudioNumChannels = 6;
const uint64_t kNanosecondsPerSecond = 1000000000ull;
} // namespace } // namespace
@ -344,6 +345,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
AudioCodec codec = FourCCToAudioCodec(actual_format); AudioCodec codec = FourCCToAudioCodec(actual_format);
uint8_t num_channels = 0; uint8_t num_channels = 0;
uint32_t sampling_frequency = 0; uint32_t sampling_frequency = 0;
uint64_t codec_delay_ns = 0;
uint8_t audio_object_type = 0; uint8_t audio_object_type = 0;
uint32_t max_bitrate = 0; uint32_t max_bitrate = 0;
uint32_t avg_bitrate = 0; uint32_t avg_bitrate = 0;
@ -424,6 +426,14 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
num_channels = entry.channelcount; num_channels = entry.channelcount;
sampling_frequency = entry.samplerate; sampling_frequency = entry.samplerate;
break; break;
case FOURCC_Opus:
extra_data = entry.dops.opus_identification_header;
num_channels = entry.channelcount;
sampling_frequency = entry.samplerate;
RCHECK(sampling_frequency != 0);
codec_delay_ns =
entry.dops.preskip * kNanosecondsPerSecond / sampling_frequency;
break;
default: default:
LOG(ERROR) << "Unsupported audio format 0x" << std::hex LOG(ERROR) << "Unsupported audio format 0x" << std::hex
<< actual_format << " in stsd box."; << actual_format << " in stsd box.";
@ -444,7 +454,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
num_channels, num_channels,
sampling_frequency, sampling_frequency,
0 /* seek preroll */, 0 /* seek preroll */,
0 /* codec delay */, codec_delay_ns,
max_bitrate, max_bitrate,
avg_bitrate, avg_bitrate,
extra_data.data(), extra_data.data(),

View File

@ -77,6 +77,8 @@ FourCC AudioCodecToFourCC(AudioCodec codec) {
return FOURCC_dtsm; return FOURCC_dtsm;
case kCodecEAC3: case kCodecEAC3:
return FOURCC_ec_3; return FOURCC_ec_3;
case kCodecOpus:
return FOURCC_Opus;
default: default:
return FOURCC_NULL; return FOURCC_NULL;
} }
@ -274,6 +276,9 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
case kCodecEAC3: case kCodecEAC3:
audio.dec3.data = audio_info->extra_data(); audio.dec3.data = audio_info->extra_data();
break; break;
case kCodecOpus:
audio.dops.opus_identification_header = audio_info->extra_data();
break;
default: default:
NOTIMPLEMENTED(); NOTIMPLEMENTED();
break; break;

View File

@ -67,7 +67,7 @@ scoped_refptr<AudioStreamInfo> WebMAudioClient::GetAudioStreamInfo(
extra_data_size = codec_private.size(); extra_data_size = codec_private.size();
} }
const uint32_t kSampleSizeInBits = 4u; const uint8_t kSampleSizeInBits = 16u;
return scoped_refptr<AudioStreamInfo>(new AudioStreamInfo( return scoped_refptr<AudioStreamInfo>(new AudioStreamInfo(
track_num, kWebMTimeScale, 0, audio_codec, track_num, kWebMTimeScale, 0, audio_codec,
AudioStreamInfo::GetCodecString(audio_codec, 0), language, AudioStreamInfo::GetCodecString(audio_codec, 0), language,