MPD signaling for Dolby Enhanced AC3 audio.

Parse channel layout value from dec3 box. Pass it through audio stream
info. MPD builder forms audio channel configuration to signal ec-3
codec.
Specs: 1) ETSI TS 102 366 V1.3.1 Digital Audio Compression (AC-3,
Enhanced AC-3) Standard E.1.3.1.8.
2) DASH-IF Interoperability Points v3.0 9.2.1.2.

Issue #64

Change-Id: Ia2c22dd3d82e757ba5a88ba1de35c5d593f5005e
This commit is contained in:
Bei Li 2016-02-08 16:22:01 -08:00
parent 38c30bd7a1
commit 9ddf9276ce
11 changed files with 288 additions and 57 deletions

View File

@ -27,6 +27,7 @@
'../../third_party/protobuf/protobuf.gyp:protobuf_full_do_not_use', '../../third_party/protobuf/protobuf.gyp:protobuf_full_do_not_use',
'../base/media_base.gyp:media_base', '../base/media_base.gyp:media_base',
'../file/file.gyp:file', '../file/file.gyp:file',
'../filters/filters.gyp:filters',
], ],
}, },
{ {

View File

@ -12,6 +12,7 @@
#include "packager/media/base/audio_stream_info.h" #include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/base/video_stream_info.h" #include "packager/media/base/video_stream_info.h"
#include "packager/media/filters/ec3_audio_util.h"
#include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/media_info.pb.h"
namespace edash_packager { namespace edash_packager {
@ -118,6 +119,16 @@ void AddAudioInfo(const AudioStreamInfo* audio_stream_info,
if (!extra_data.empty()) { if (!extra_data.empty()) {
audio_info->set_decoder_config(&extra_data[0], extra_data.size()); audio_info->set_decoder_config(&extra_data[0], extra_data.size());
} }
if (audio_stream_info->codec_string() == "ec-3") {
uint32_t ec3_channel_map;
if (!CalculateEC3ChannelMap(extra_data, &ec3_channel_map)) {
LOG(ERROR) << "Failed to calculate EC3 channel map.";
return;
}
audio_info->mutable_codec_specific_data()->set_ec3_channel_map(
ec3_channel_map);
}
} }
void SetMediaInfoStreamInfo(const StreamInfo& stream_info, void SetMediaInfoStreamInfo(const StreamInfo& stream_info,

View File

@ -0,0 +1,155 @@
// Copyright 2016 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/base/bit_reader.h"
#include "packager/media/filters/ec3_audio_util.h"
#include "packager/media/formats/mp4/rcheck.h"
namespace edash_packager {
namespace media {
namespace {
// Channels bit map. 16 bits.
// Bit, Location
// 0(MSB), Left
// 1, Center
// 2, Right
// 3, Left Surround
// 4, Right Surround
// 5, Left center/Right center pair
// 6, Left rear surround/Right rear surround pair
// 7, Center surround
// 8, Top center surround
// 9, Left surround direct/Right surround direct pair
// 10, Left wide/Right wide pair
// 11, Lvertical height/Right vertical height pair
// 12, Center vertical height
// 13, Lts/Rts pair
// 14, LFE2
// 15, LFE
enum kEC3AudioChannelMap {
kLeft = 0x8000,
kCenter = 0x4000,
kRight = 0x2000,
kLeftSurround = 0x1000,
kRightSurround = 0x800,
kLcRcPair = 0x400,
kLrsRrsPair = 0x200,
kCenterSurround = 0x100,
kTopCenterSurround = 0x80,
kLsdRsdPair = 0x40,
kLwRwPair = 0x20,
kLvhRvhPair = 0x10,
kCenterVerticalHeight = 0x8,
kLtsRtsPair = 0x4,
kLFE2 = 0x2,
kLFEScreen = 0x1
};
// EC3 Audio coding mode map (acmod) to determine EC3 audio channel layout. The
// value stands for the existence of Left, Center, Right, Left surround, and
// Right surround.
const uint16_t kEC3AudioCodingModeMap[] = {
kLeft | kRight,
kCenter,
kLeft | kRight,
kLeft | kCenter | kRight,
kLeft | kRight | kLeftSurround | kRightSurround,
kLeft | kCenter | kRight | kLeftSurround | kRightSurround,
kLeft | kRight | kLeftSurround | kRightSurround,
kLeft | kCenter | kRight | kLeftSurround | kRightSurround,
};
// Reverse bit order.
uint8_t ReverseBits8(uint8_t n) {
n = ((n >> 1) & 0x55) | ((n & 0x55) << 1);
n = ((n >> 2) & 0x33) | ((n & 0x33) << 2);
return ((n >> 4) & 0x0f) | ((n & 0x0f) << 4);
}
bool ExtractEc3Data(const std::vector<uint8_t>& ec3_data,
uint8_t* audio_coding_mode,
bool* lfe_channel_on,
uint16_t* dependent_substreams_layout) {
BitReader bit_reader(ec3_data.data(), ec3_data.size());
// Read number of independent substreams and parse the independent substreams.
uint8_t number_independent_substreams;
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;
// Parse audio_coding_mode, dependent_substreams_layout and lfe_channel_on
// from the first independent substream.
// Independent substream in EC3Specific box:
// fscod: 2 bits
// bsid: 5 bits
// reserved_1: 1 bit
// asvc: 1 bit
// bsmod: 3 bits
// acmod: 3 bits
// lfeon: 1 bit
// reserved_2: 3 bits
// 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.
// chan_loc: 9 bits
// reserved_3: 1 bit
RCHECK(bit_reader.SkipBits(12));
RCHECK(bit_reader.ReadBits(3, audio_coding_mode));
RCHECK(bit_reader.ReadBits(1, lfe_channel_on));
uint8_t number_dependent_substreams = 0;
RCHECK(bit_reader.SkipBits(3));
RCHECK(bit_reader.ReadBits(4, &number_dependent_substreams));
*dependent_substreams_layout = 0;
if (number_dependent_substreams > 0) {
RCHECK(bit_reader.ReadBits(9, dependent_substreams_layout));
}
return true;
}
} // namespace
bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data, uint32_t* channel_map) {
uint8_t audio_coding_mode;
bool lfe_channel_on;
uint16_t dependent_substreams_layout;
if (!ExtractEc3Data(ec3_data, &audio_coding_mode, &lfe_channel_on,
&dependent_substreams_layout))
return false;
// Dependent substreams layout bit map:
// Bit, Location
// 0, Lc/Rc pair
// 1, Lrs/Rrs pair
// 2, Cs
// 3, Ts
// 4, Lsd/Rsd pair
// 5, Lw/Rw pair
// 6, Lvh/Rvh pair
// 7, Cvh
// 8(MSB), LFE2
// Reverse bit order of dependent substreams channel layout (LFE2 not
// included) to apply on channel_map bit 5 - 12.
const uint8_t reversed_dependent_substreams_layout =
ReverseBits8(dependent_substreams_layout & 0xFF);
*channel_map = kEC3AudioCodingModeMap[audio_coding_mode] |
(reversed_dependent_substreams_layout << 3);
if (dependent_substreams_layout & 0x100)
*channel_map |= kLFE2;
if (lfe_channel_on)
*channel_map |= kLFEScreen;
return true;
}
} // namespace media
} // namespace edash_packager

View File

@ -0,0 +1,27 @@
// Copyright 2016 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
//
// Enhanced AC3 audio utility functions.
#ifndef MEDIA_FILTERS_EC3_AUDIO_UTIL_H_
#define MEDIA_FILTERS_EC3_AUDIO_UTIL_H_
#include <stdint.h>
#include <vector>
namespace edash_packager {
namespace media {
/// Parse data from EC3Specific box and calculate EC3 channel map value based on
/// ETSI TS 102 366 V1.3.1 Digital Audio Compression (AC-3, Enhanced AC-3)
/// Standard E.1.3.1.8.
bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data,
uint32_t* channel_map);
} // namespace media
} // namespace edash_packager
#endif // MEDIA_FILTERS_EC3_AUDIO_UTIL_H_

View File

@ -0,0 +1,43 @@
// Copyright 2016 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include <gtest/gtest.h>
#include "packager/media/filters/ec3_audio_util.h"
namespace edash_packager {
namespace media {
TEST(EC3AudioUtilTest, CalculateEC3ChannelMapTest1) {
// audio_coding_mode is 7, which is Left, Center, Right, Left surround, Right
// surround. No dependent substreams. LFE channel on.
const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x0f, 0};
uint32_t channel_map;
EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map));
EXPECT_EQ(0xF801u, channel_map);
}
TEST(EC3AudioUtilTest, CalculateEC3ChannelMapTest2) {
// audio_coding_mode is 2, which is Left and Right. No dependent substreams.
// LFE channel off.
const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x04, 0};
uint32_t channel_map;
EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map));
EXPECT_EQ(0xA000u, channel_map);
}
TEST(EC3AudioUtilTest, CalculateEC3ChannelMapTest3) {
// audio_coding_mode is 3, which is Left, Center, and Right. Dependent
// substreams layout is 0b100000011, which is Left center/ Right center pair,
// Left rear surround/ Right rear surround pair, LFE2 on. LFE channel on.
const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x07, 0x07, 0x03};
uint32_t channel_map;
EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map));
EXPECT_EQ(0xE603u, channel_map);
}
} // namespace media
} // namespace edash_packager

View File

@ -15,6 +15,8 @@
'sources': [ 'sources': [
'avc_decoder_configuration.cc', 'avc_decoder_configuration.cc',
'avc_decoder_configuration.h', 'avc_decoder_configuration.h',
'ec3_audio_util.cc',
'ec3_audio_util.h',
'hevc_decoder_configuration.cc', 'hevc_decoder_configuration.cc',
'hevc_decoder_configuration.h', 'hevc_decoder_configuration.h',
'h264_bit_reader.cc', 'h264_bit_reader.cc',
@ -40,6 +42,7 @@
'type': '<(gtest_target_type)', 'type': '<(gtest_target_type)',
'sources': [ 'sources': [
'avc_decoder_configuration_unittest.cc', 'avc_decoder_configuration_unittest.cc',
'ec3_audio_util_unittest.cc',
'h264_bit_reader_unittest.cc', 'h264_bit_reader_unittest.cc',
'h264_byte_to_unit_stream_converter_unittest.cc', 'h264_byte_to_unit_stream_converter_unittest.cc',
'h264_parser_unittest.cc', 'h264_parser_unittest.cc',

View File

@ -1439,7 +1439,7 @@ uint32_t AC3Specific::ComputeSizeInternal() {
return HeaderSize() + data.size(); return HeaderSize() + data.size();
} }
EC3Specific::EC3Specific() : number_independent_substreams(0) {} EC3Specific::EC3Specific() {}
EC3Specific::~EC3Specific() {} EC3Specific::~EC3Specific() {}
FourCC EC3Specific::BoxType() const { return FOURCC_DEC3; } FourCC EC3Specific::BoxType() const { return FOURCC_DEC3; }
@ -1448,35 +1448,6 @@ bool EC3Specific::ReadWriteInternal(BoxBuffer* buffer) {
RCHECK(ReadWriteHeaderInternal(buffer)); RCHECK(ReadWriteHeaderInternal(buffer));
uint32_t size = buffer->Reading() ? buffer->BytesLeft() : data.size(); uint32_t size = buffer->Reading() ? buffer->BytesLeft() : data.size();
RCHECK(buffer->ReadWriteVector(&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; return true;
} }

View File

@ -315,32 +315,10 @@ 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 { struct EC3Specific : Box {
DECLARE_BOX_METHODS(EC3Specific); 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; 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 {

View File

@ -49,6 +49,13 @@ message MediaInfo {
optional uint32 num_channels = 4; optional uint32 num_channels = 4;
optional string language = 5; optional string language = 5;
optional bytes decoder_config = 6; optional bytes decoder_config = 6;
optional AudioCodecSpecificData codec_specific_data = 7;
}
message AudioCodecSpecificData {
// EC3 Channel map bit fields, encoded based on ETSI TS 102 366 V1.3.1
// Digital Audio Compression (AC-3, Enhanced AC-3) Standard E.1.3.1.8.
optional uint32 ec3_channel_map = 1;
} }
message TextInfo { message TextInfo {

View File

@ -12,6 +12,7 @@
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/macros.h" #include "packager/base/macros.h"
#include "packager/base/stl_util.h" #include "packager/base/stl_util.h"
#include "packager/base/sys_byteorder.h"
#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_number_conversions.h"
#include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/segment_info.h" #include "packager/mpd/base/segment_info.h"
@ -27,6 +28,7 @@ typedef ContentProtectionXml::AttributeNameValuePair AttributeNameValuePair;
namespace edash_packager { namespace edash_packager {
namespace { namespace {
const char kEC3Codec[] = "ec-3";
std::string RangeToString(const Range& range) { std::string RangeToString(const Range& range) {
return base::Uint64ToString(range.begin()) + "-" + return base::Uint64ToString(range.begin()) + "-" +
@ -343,13 +345,28 @@ bool RepresentationXmlNode::AddLiveOnlyInfo(
} }
bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) { bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
const uint32_t num_channels = audio_info.num_channels(); std::string audio_channel_config_scheme;
std::string audio_channel_config_value;
if (audio_info.codec() == kEC3Codec) {
// Convert EC3 channel map into string of hexadecimal digits. Spec: DASH-IF
// Interoperability Points v3.0 9.2.1.2.
const uint16_t ec3_channel_map =
base::HostToNet16(audio_info.codec_specific_data().ec3_channel_map());
audio_channel_config_value =
base::HexEncode(&ec3_channel_map, sizeof(ec3_channel_map));
audio_channel_config_scheme =
"tag:dolby.com,2014:dash:audio_channel_configuration:2011";
} else {
audio_channel_config_value = base::UintToString(audio_info.num_channels());
audio_channel_config_scheme =
"urn:mpeg:dash:23003:3:audio_channel_configuration:2011";
}
XmlNode audio_channel_config("AudioChannelConfiguration"); XmlNode audio_channel_config("AudioChannelConfiguration");
const char kAudioChannelConfigScheme[] =
"urn:mpeg:dash:23003:3:audio_channel_configuration:2011";
audio_channel_config.SetStringAttribute("schemeIdUri", audio_channel_config.SetStringAttribute("schemeIdUri",
kAudioChannelConfigScheme); audio_channel_config_scheme);
audio_channel_config.SetIntegerAttribute("value", num_channels); audio_channel_config.SetStringAttribute("value", audio_channel_config_value);
return AddChild(audio_channel_config.PassScopedPtr()); return AddChild(audio_channel_config.PassScopedPtr());
} }

View File

@ -217,6 +217,24 @@ TEST_F(RepresentationTest, AddContentProtectionElements) {
doc.get())); doc.get()));
} }
TEST_F(RepresentationTest, AddEC3AudioInfo) {
MediaInfo::AudioInfo audio_info;
audio_info.set_codec("ec-3");
audio_info.set_sampling_frequency(44100);
audio_info.mutable_codec_specific_data()->set_ec3_channel_map(0xF801);
representation_.AddAudioInfo(audio_info);
scoped_xml_ptr<xmlDoc> doc(MakeDoc(representation_.PassScopedPtr()));
ASSERT_TRUE(XmlEqual(
"<Representation audioSamplingRate=\"44100\">\n"
" <AudioChannelConfiguration\n"
" schemeIdUri=\n"
" \"tag:dolby.com,2014:dash:audio_channel_configuration:2011\"\n"
" value=\"F801\"/>\n"
"</Representation>\n",
doc.get()));
}
// Some template names cannot be used for init segment name. // Some template names cannot be used for init segment name.
TEST_F(RepresentationTest, InvalidLiveInitSegmentName) { TEST_F(RepresentationTest, InvalidLiveInitSegmentName) {
MediaInfo media_info; MediaInfo media_info;