Support Dolby audio Enhanced AC3 in ISO BMFF (Part 2)
- Box definitions for box type DEC3. - Parser/muxer changes to support Enhanced-AC3 audio codecs. - MPD signaling will come in Part 3. Issue #64 Change-Id: Ifcd5efa1f61b470ec225127925631e4329853259
This commit is contained in:
parent
d1d75f477c
commit
8563db4cff
|
@ -33,6 +33,8 @@ std::string AudioCodecToString(AudioCodec audio_codec) {
|
||||||
return "DTS-";
|
return "DTS-";
|
||||||
case kCodecDTSP:
|
case kCodecDTSP:
|
||||||
return "DTS+";
|
return "DTS+";
|
||||||
|
case kCodecEAC3:
|
||||||
|
return "EAC3";
|
||||||
case kCodecOpus:
|
case kCodecOpus:
|
||||||
return "Opus";
|
return "Opus";
|
||||||
case kCodecVorbis:
|
case kCodecVorbis:
|
||||||
|
@ -115,6 +117,8 @@ std::string AudioStreamInfo::GetCodecString(AudioCodec codec,
|
||||||
return "dts-";
|
return "dts-";
|
||||||
case kCodecAC3:
|
case kCodecAC3:
|
||||||
return "ac-3";
|
return "ac-3";
|
||||||
|
case kCodecEAC3:
|
||||||
|
return "ec-3";
|
||||||
default:
|
default:
|
||||||
NOTIMPLEMENTED() << "Codec: " << codec;
|
NOTIMPLEMENTED() << "Codec: " << codec;
|
||||||
return "unknown";
|
return "unknown";
|
||||||
|
|
|
@ -24,6 +24,7 @@ enum AudioCodec {
|
||||||
kCodecDTSL,
|
kCodecDTSL,
|
||||||
kCodecDTSM,
|
kCodecDTSM,
|
||||||
kCodecDTSP,
|
kCodecDTSP,
|
||||||
|
kCodecEAC3,
|
||||||
kCodecOpus,
|
kCodecOpus,
|
||||||
kCodecVorbis,
|
kCodecVorbis,
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
#include "packager/media/base/bit_reader.h"
|
#include "packager/media/base/bit_reader.h"
|
||||||
|
#include "packager/media/base/macros.h"
|
||||||
#include "packager/media/formats/mp4/box_buffer.h"
|
#include "packager/media/formats/mp4/box_buffer.h"
|
||||||
#include "packager/media/formats/mp4/rcheck.h"
|
#include "packager/media/formats/mp4/rcheck.h"
|
||||||
|
|
||||||
|
@ -1435,6 +1436,54 @@ uint32_t AC3Specific::ComputeSizeInternal() {
|
||||||
return HeaderSize() + data.size();
|
return HeaderSize() + data.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EC3Specific::EC3Specific() : number_independent_substreams(0) {}
|
||||||
|
EC3Specific::~EC3Specific() {}
|
||||||
|
|
||||||
|
FourCC EC3Specific::BoxType() const { return FOURCC_DEC3; }
|
||||||
|
|
||||||
|
bool EC3Specific::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
|
RCHECK(ReadWriteHeaderInternal(buffer));
|
||||||
|
uint32_t size = buffer->Reading() ? buffer->BytesLeft() : data.size();
|
||||||
|
RCHECK(buffer->ReadWriteVector(&data, size));
|
||||||
|
|
||||||
|
// Skip data rate, read number of independent substreams and parse the
|
||||||
|
// independent substreams.
|
||||||
|
BitReader bit_reader(&data[0], size);
|
||||||
|
RCHECK(bit_reader.SkipBits(13) &&
|
||||||
|
bit_reader.ReadBits(3, &number_independent_substreams));
|
||||||
|
|
||||||
|
// The value of this field is one less than the number of independent
|
||||||
|
// substreams present.
|
||||||
|
++number_independent_substreams;
|
||||||
|
IndependentSubstream substream;
|
||||||
|
for (size_t i = 0; i < number_independent_substreams; ++i) {
|
||||||
|
RCHECK(bit_reader.ReadBits(2, &substream.sample_rate_code));
|
||||||
|
RCHECK(bit_reader.ReadBits(5, &substream.bit_stream_identification));
|
||||||
|
RCHECK(bit_reader.SkipBits(1));
|
||||||
|
RCHECK(bit_reader.ReadBits(1, &substream.audio_service));
|
||||||
|
RCHECK(bit_reader.ReadBits(3, &substream.bit_stream_mode));
|
||||||
|
RCHECK(bit_reader.ReadBits(3, &substream.audio_coding_mode));
|
||||||
|
RCHECK(bit_reader.ReadBits(1, &substream.lfe_channel_on));
|
||||||
|
RCHECK(bit_reader.SkipBits(3));
|
||||||
|
RCHECK(bit_reader.ReadBits(4, &substream.number_dependent_substreams));
|
||||||
|
if (substream.number_dependent_substreams > 0) {
|
||||||
|
RCHECK(bit_reader.ReadBits(9, &substream.channel_location));
|
||||||
|
} else {
|
||||||
|
RCHECK(bit_reader.SkipBits(1));
|
||||||
|
}
|
||||||
|
independent_substreams.push_back(substream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EC3Specific::ComputeSizeInternal() {
|
||||||
|
// This box is optional. Skip it if not initialized.
|
||||||
|
if (data.empty())
|
||||||
|
return 0;
|
||||||
|
return HeaderSize() + data.size();
|
||||||
|
}
|
||||||
|
|
||||||
AudioSampleEntry::AudioSampleEntry()
|
AudioSampleEntry::AudioSampleEntry()
|
||||||
: format(FOURCC_NULL),
|
: format(FOURCC_NULL),
|
||||||
data_reference_index(1),
|
data_reference_index(1),
|
||||||
|
@ -1489,6 +1538,7 @@ bool AudioSampleEntry::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
RCHECK(buffer->TryReadWriteChild(&esds));
|
RCHECK(buffer->TryReadWriteChild(&esds));
|
||||||
RCHECK(buffer->TryReadWriteChild(&ddts));
|
RCHECK(buffer->TryReadWriteChild(&ddts));
|
||||||
RCHECK(buffer->TryReadWriteChild(&dac3));
|
RCHECK(buffer->TryReadWriteChild(&dac3));
|
||||||
|
RCHECK(buffer->TryReadWriteChild(&dec3));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1496,6 +1546,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() +
|
||||||
6 + 8 + // 6 + 8 bytes reserved.
|
6 + 8 + // 6 + 8 bytes reserved.
|
||||||
4; // 4 bytes predefined.
|
4; // 4 bytes predefined.
|
||||||
}
|
}
|
||||||
|
|
|
@ -315,6 +315,34 @@ struct AC3Specific : Box {
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Independent substream in EC3Specific box.
|
||||||
|
struct IndependentSubstream {
|
||||||
|
uint8_t sample_rate_code; // fscod: 2 bits
|
||||||
|
uint8_t bit_stream_identification; // bsid: 5 bits
|
||||||
|
// reserved_1: 1 bit
|
||||||
|
uint8_t audio_service; // asvc: 1 bit
|
||||||
|
uint8_t bit_stream_mode; // bsmod: 3 bits
|
||||||
|
uint8_t audio_coding_mode; // acmod: 3 bits
|
||||||
|
uint8_t lfe_channel_on; // lfeon: 1 bit
|
||||||
|
// reserved_2: 3 bit
|
||||||
|
uint8_t number_dependent_substreams; // num_dep_sub: 4 bits.
|
||||||
|
// If num_dep_sub > 0, chan_loc is present and the size is 9 bits.
|
||||||
|
// Otherwise, reserved_3 is present and the size is 1 bit.
|
||||||
|
uint16_t channel_location; // chan_loc: 9 bits.
|
||||||
|
// reserved_3: 1 bit
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EC3Specific : Box {
|
||||||
|
DECLARE_BOX_METHODS(EC3Specific);
|
||||||
|
|
||||||
|
// Before we know the number of independent substreams, data in EC3Specific
|
||||||
|
// box is store for parsing later.
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
|
||||||
|
size_t number_independent_substreams; // num_id_sub: 3 bits.
|
||||||
|
std::vector<IndependentSubstream> independent_substreams;
|
||||||
|
};
|
||||||
|
|
||||||
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.
|
||||||
|
@ -329,9 +357,11 @@ struct AudioSampleEntry : Box {
|
||||||
uint32_t samplerate;
|
uint32_t samplerate;
|
||||||
|
|
||||||
ProtectionSchemeInfo sinf;
|
ProtectionSchemeInfo sinf;
|
||||||
|
|
||||||
ElementaryStreamDescriptor esds;
|
ElementaryStreamDescriptor esds;
|
||||||
DTSSpecific ddts;
|
DTSSpecific ddts;
|
||||||
AC3Specific dac3;
|
AC3Specific dac3;
|
||||||
|
EC3Specific dec3;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebVTTConfigurationBox : Box {
|
struct WebVTTConfigurationBox : Box {
|
||||||
|
|
|
@ -245,6 +245,11 @@ inline bool operator==(const AC3Specific& lhs,
|
||||||
return lhs.data == rhs.data;
|
return lhs.data == rhs.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool operator==(const EC3Specific& lhs,
|
||||||
|
const EC3Specific& rhs) {
|
||||||
|
return lhs.data == rhs.data;
|
||||||
|
}
|
||||||
|
|
||||||
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 &&
|
||||||
|
@ -252,7 +257,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.dac3 == rhs.dac3 && lhs.dec3 == rhs.dec3;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool operator==(const WebVTTConfigurationBox& lhs,
|
inline bool operator==(const WebVTTConfigurationBox& lhs,
|
||||||
|
|
|
@ -390,6 +390,16 @@ class BoxDefinitionsTestGeneral : public testing::Test {
|
||||||
dac3->data.assign(kAc3Data, kAc3Data + arraysize(kAc3Data));
|
dac3->data.assign(kAc3Data, kAc3Data + arraysize(kAc3Data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Fill(EC3Specific* dec3) {
|
||||||
|
const uint8_t kEc3Data[] = {0x08, 0x00, 0x20, 0x0f, 0x00};
|
||||||
|
dec3->data.assign(kEc3Data, kEc3Data + arraysize(kEc3Data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Modify(EC3Specific* dec3) {
|
||||||
|
const uint8_t kEc3Data[] = {0x07, 0x00, 0x60, 0x04, 0x00};
|
||||||
|
dec3->data.assign(kEc3Data, kEc3Data + arraysize(kEc3Data));
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -895,6 +905,7 @@ class BoxDefinitionsTestGeneral : public testing::Test {
|
||||||
bool IsOptional(const PixelAspectRatio* box) { return true; }
|
bool IsOptional(const PixelAspectRatio* box) { return true; }
|
||||||
bool IsOptional(const ElementaryStreamDescriptor* box) { return true; }
|
bool IsOptional(const ElementaryStreamDescriptor* box) { return true; }
|
||||||
bool IsOptional(const AC3Specific* box) { return true; }
|
bool IsOptional(const AC3Specific* box) { return true; }
|
||||||
|
bool IsOptional(const EC3Specific* box) { return true; }
|
||||||
// Recommended, but optional.
|
// Recommended, but optional.
|
||||||
bool IsOptional(const WebVTTSourceLabelBox* box) { return true; }
|
bool IsOptional(const WebVTTSourceLabelBox* box) { return true; }
|
||||||
bool IsOptional(const CompositionTimeToSample* box) { return true; }
|
bool IsOptional(const CompositionTimeToSample* box) { return true; }
|
||||||
|
@ -938,6 +949,7 @@ typedef testing::Types<FileType,
|
||||||
ElementaryStreamDescriptor,
|
ElementaryStreamDescriptor,
|
||||||
DTSSpecific,
|
DTSSpecific,
|
||||||
AC3Specific,
|
AC3Specific,
|
||||||
|
EC3Specific,
|
||||||
AudioSampleEntry,
|
AudioSampleEntry,
|
||||||
WebVTTConfigurationBox,
|
WebVTTConfigurationBox,
|
||||||
WebVTTSourceLabelBox,
|
WebVTTSourceLabelBox,
|
||||||
|
@ -1078,7 +1090,6 @@ TEST_F(BoxDefinitionsTest, DTSSampleEntry) {
|
||||||
entry.samplerate = 44100;
|
entry.samplerate = 44100;
|
||||||
Fill(&entry.ddts);
|
Fill(&entry.ddts);
|
||||||
entry.Write(this->buffer_.get());
|
entry.Write(this->buffer_.get());
|
||||||
|
|
||||||
AudioSampleEntry entry_readback;
|
AudioSampleEntry entry_readback;
|
||||||
ASSERT_TRUE(ReadBack(&entry_readback));
|
ASSERT_TRUE(ReadBack(&entry_readback));
|
||||||
ASSERT_EQ(entry, entry_readback);
|
ASSERT_EQ(entry, entry_readback);
|
||||||
|
@ -1099,6 +1110,21 @@ TEST_F(BoxDefinitionsTest, AC3SampleEntry) {
|
||||||
ASSERT_EQ(entry, entry_readback);
|
ASSERT_EQ(entry, entry_readback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(BoxDefinitionsTest, EC3SampleEntry) {
|
||||||
|
AudioSampleEntry entry;
|
||||||
|
entry.format = FOURCC_EAC3;
|
||||||
|
entry.data_reference_index = 2;
|
||||||
|
entry.channelcount = 5;
|
||||||
|
entry.samplesize = 16;
|
||||||
|
entry.samplerate = 44100;
|
||||||
|
Fill(&entry.dec3);
|
||||||
|
entry.Write(this->buffer_.get());
|
||||||
|
|
||||||
|
AudioSampleEntry entry_readback;
|
||||||
|
ASSERT_TRUE(ReadBack(&entry_readback));
|
||||||
|
ASSERT_EQ(entry, entry_readback);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(BoxDefinitionsTest, ProtectionSystemSpecificHeader) {
|
TEST_F(BoxDefinitionsTest, ProtectionSystemSpecificHeader) {
|
||||||
ProtectionSystemSpecificHeader pssh;
|
ProtectionSystemSpecificHeader pssh;
|
||||||
Fill(&pssh);
|
Fill(&pssh);
|
||||||
|
|
|
@ -26,6 +26,7 @@ enum FourCC {
|
||||||
FOURCC_DAC3 = 0x64616333,
|
FOURCC_DAC3 = 0x64616333,
|
||||||
FOURCC_DASH = 0x64617368,
|
FOURCC_DASH = 0x64617368,
|
||||||
FOURCC_DDTS = 0x64647473,
|
FOURCC_DDTS = 0x64647473,
|
||||||
|
FOURCC_DEC3 = 0x64656333,
|
||||||
FOURCC_DINF = 0x64696e66,
|
FOURCC_DINF = 0x64696e66,
|
||||||
FOURCC_DREF = 0x64726566,
|
FOURCC_DREF = 0x64726566,
|
||||||
FOURCC_DTSC = 0x64747363,
|
FOURCC_DTSC = 0x64747363,
|
||||||
|
@ -34,6 +35,7 @@ enum FourCC {
|
||||||
FOURCC_DTSL = 0x6474736c,
|
FOURCC_DTSL = 0x6474736c,
|
||||||
FOURCC_DTSM = 0x6474732d,
|
FOURCC_DTSM = 0x6474732d,
|
||||||
FOURCC_DTSP = 0x6474732b,
|
FOURCC_DTSP = 0x6474732b,
|
||||||
|
FOURCC_EAC3 = 0x65632d33, // This fourcc is "ec-3".
|
||||||
FOURCC_EDTS = 0x65647473,
|
FOURCC_EDTS = 0x65647473,
|
||||||
FOURCC_ELST = 0x656c7374,
|
FOURCC_ELST = 0x656c7374,
|
||||||
FOURCC_ENCA = 0x656e6361,
|
FOURCC_ENCA = 0x656e6361,
|
||||||
|
|
|
@ -76,6 +76,8 @@ AudioCodec FourCCToAudioCodec(FourCC fourcc) {
|
||||||
return kCodecDTSM;
|
return kCodecDTSM;
|
||||||
case FOURCC_AC3:
|
case FOURCC_AC3:
|
||||||
return kCodecAC3;
|
return kCodecAC3;
|
||||||
|
case FOURCC_EAC3:
|
||||||
|
return kCodecEAC3;
|
||||||
default:
|
default:
|
||||||
return kUnknownAudioCodec;
|
return kUnknownAudioCodec;
|
||||||
}
|
}
|
||||||
|
@ -415,6 +417,11 @@ 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_EAC3:
|
||||||
|
extra_data = entry.dec3.data;
|
||||||
|
num_channels = entry.channelcount;
|
||||||
|
sampling_frequency = entry.samplerate;
|
||||||
|
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.";
|
||||||
|
|
|
@ -75,6 +75,8 @@ FourCC AudioCodecToFourCC(AudioCodec codec) {
|
||||||
return FOURCC_DTSE;
|
return FOURCC_DTSE;
|
||||||
case kCodecDTSM:
|
case kCodecDTSM:
|
||||||
return FOURCC_DTSM;
|
return FOURCC_DTSM;
|
||||||
|
case kCodecEAC3:
|
||||||
|
return FOURCC_EAC3;
|
||||||
default:
|
default:
|
||||||
return FOURCC_NULL;
|
return FOURCC_NULL;
|
||||||
}
|
}
|
||||||
|
@ -266,6 +268,9 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
|
||||||
case kCodecAC3:
|
case kCodecAC3:
|
||||||
audio.dac3.data = audio_info->extra_data();
|
audio.dac3.data = audio_info->extra_data();
|
||||||
break;
|
break;
|
||||||
|
case kCodecEAC3:
|
||||||
|
audio.dec3.data = audio_info->extra_data();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
NOTIMPLEMENTED();
|
NOTIMPLEMENTED();
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue