From 71a425799d0602fbf9796b6b7a6f4b24635b7d5b Mon Sep 17 00:00:00 2001 From: Thomas Inskip Date: Tue, 15 Apr 2014 16:51:32 -0700 Subject: [PATCH] Added ADTS header parsing, removal, and conversion to AudioSpecificConfig. Hooked up mp2t media parser, audio transmux now working. Change-Id: Idc92a51d25fa6ed2f74627131d53919e52394119 --- media/base/demuxer.cc | 4 + media/filters/filters.gyp | 2 +- media/formats/mp2t/adts_header.cc | 114 +++++++++++++++++++++ media/formats/mp2t/adts_header.h | 72 +++++++++++++ media/formats/mp2t/adts_header_unittest.cc | 91 ++++++++++++++++ media/formats/mp2t/es_parser_adts.cc | 94 ++++++++--------- media/formats/mp2t/es_parser_adts.h | 2 +- media/formats/mp2t/mp2t.gyp | 3 + packager.gyp | 7 ++ 9 files changed, 334 insertions(+), 55 deletions(-) create mode 100644 media/formats/mp2t/adts_header.cc create mode 100644 media/formats/mp2t/adts_header.h create mode 100644 media/formats/mp2t/adts_header_unittest.cc diff --git a/media/base/demuxer.cc b/media/base/demuxer.cc index 5480039058..8bbce8053f 100644 --- a/media/base/demuxer.cc +++ b/media/base/demuxer.cc @@ -15,6 +15,7 @@ #include "media/base/media_stream.h" #include "media/base/stream_info.h" #include "media/file/file.h" +#include "media/formats/mp2t/mp2t_media_parser.h" #include "media/formats/mp4/mp4_media_parser.h" namespace { @@ -58,6 +59,9 @@ Status Demuxer::Initialize() { case CONTAINER_MOV: parser_.reset(new mp4::MP4MediaParser()); break; + case CONTAINER_MPEG2TS: + parser_.reset(new mp2t::MediaParser()); + break; default: NOTIMPLEMENTED(); return Status(error::UNIMPLEMENTED, "Container not supported."); diff --git a/media/filters/filters.gyp b/media/filters/filters.gyp index 5ce9a44ddb..d4d1ace796 100644 --- a/media/filters/filters.gyp +++ b/media/filters/filters.gyp @@ -36,8 +36,8 @@ 'h264_parser_unittest.cc', ], 'dependencies': [ + '../../media/base/media_base.gyp:base', '../../testing/gtest.gyp:gtest', - '../../testing/gtest.gyp:gtest_main', '../test/media_test.gyp:media_test_support', 'filters', ], diff --git a/media/formats/mp2t/adts_header.cc b/media/formats/mp2t/adts_header.cc new file mode 100644 index 0000000000..ca6b9d3037 --- /dev/null +++ b/media/formats/mp2t/adts_header.cc @@ -0,0 +1,114 @@ +// Copyright 2014 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 "adts_header.h" + +#include "media/base/bit_reader.h" +#include "media/formats/mp2t/mp2t_common.h" +#include "media/formats/mpeg/adts_constants.h" + +namespace media { +namespace mp2t { + +AdtsHeader::AdtsHeader() + : valid_config_(false), + profile_(0), + sampling_frequency_index_(0), + channel_configuration_(0) {} + +size_t AdtsHeader::GetAdtsFrameSize(const uint8* data, size_t num_bytes) { + if (num_bytes < 6) + return 0; + return ((static_cast(data[5]) >> 5) | + (static_cast(data[4]) << 3) | + ((static_cast(data[3]) & 0x3) << 11)); +} + +size_t AdtsHeader::GetAdtsHeaderSize(const uint8* data, size_t num_bytes) { + if (num_bytes < 2) + return 0; + if (data[1] & 0x01) + return kAdtsHeaderMinSize; + return kAdtsHeaderMinSize + sizeof(uint16); // Header + CRC. +} + +bool AdtsHeader::Parse( + const uint8* adts_frame, size_t adts_frame_size) { + CHECK(adts_frame); + + valid_config_ = false; + + BitReader frame(adts_frame, adts_frame_size); + // Verify frame starts with sync bits (0xfff). + uint32 sync; + RCHECK(frame.ReadBits(12, &sync)); + RCHECK(sync == 0xfff); + // Skip MPEG version and layer. + RCHECK(frame.SkipBits(3)); + // Get "protection absent" flag. + bool protection_absent; + RCHECK(frame.ReadBits(1, &protection_absent)); + // Get profile. + RCHECK(frame.ReadBits(2, &profile_)); + // Get sampling frequency. + RCHECK(frame.ReadBits(4, &sampling_frequency_index_)); + RCHECK(sampling_frequency_index_ < kAdtsFrequencyTableSize); + // Skip private stream bit. + RCHECK(frame.SkipBits(1)); + // Get number of audio channels. + RCHECK(frame.ReadBits(3, &channel_configuration_)); + RCHECK((channel_configuration_ > 0) && + (channel_configuration_ < kAdtsNumChannelsTableSize)); + // Skip originality, home and copyright info. + RCHECK(frame.SkipBits(4)); + // Verify that the frame size matches input parameters. + uint16 frame_size; + RCHECK(frame.ReadBits(13, &frame_size)); + RCHECK(frame_size == adts_frame_size); + // Skip buffer fullness indicator. + RCHECK(frame.SkipBits(11)); + uint8 num_blocks_minus_1; + RCHECK(frame.ReadBits(2, &num_blocks_minus_1)); + if (num_blocks_minus_1) { + NOTIMPLEMENTED() << "ADTS frames with more than one data block " + "not supported."; + return false; + } + + valid_config_ = true; + return true; +} + +bool AdtsHeader::GetAudioSpecificConfig( + std::vector* buffer) const { + DCHECK(buffer); + if (!valid_config_) + return false; + + buffer->resize(2); + (*buffer)[0] = ((profile_ + 1) << 3) | (sampling_frequency_index_ >> 1); + (*buffer)[1] = ((sampling_frequency_index_ & 1) << 7) | + (channel_configuration_ << 3); + return true; +} + +uint8 AdtsHeader::GetObjectType() const { + return profile_ + 1; +} + +uint32 AdtsHeader::GetSamplingFrequency() const { + DCHECK_LT(sampling_frequency_index_, kAdtsFrequencyTableSize); + return kAdtsFrequencyTable[sampling_frequency_index_]; +} + +uint8 AdtsHeader::GetNumChannels() const { + DCHECK_GT(channel_configuration_, 0); + DCHECK_LT(channel_configuration_, kAdtsNumChannelsTableSize); + return kAdtsNumChannelsTable[channel_configuration_]; +} + +} // namespace mp2t +} // namespace media diff --git a/media/formats/mp2t/adts_header.h b/media/formats/mp2t/adts_header.h new file mode 100644 index 0000000000..e42ce42e22 --- /dev/null +++ b/media/formats/mp2t/adts_header.h @@ -0,0 +1,72 @@ +// Copyright 2014 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 + +#ifndef MEDIA_FORMATS_MP2T_ADTS_HEADER_H_ +#define MEDIA_FORMATS_MP2T_ADTS_HEADER_H_ + +#include "base/basictypes.h" + +#include + +namespace media { +namespace mp2t { + +/// Class which parses ADTS headers and synthesizes AudioSpecificConfig +/// and audio mime type from ADTS header contents. +class AdtsHeader { + public: + AdtsHeader(); + ~AdtsHeader() {} + + /// Get the size of the ADTS frame from a partial or complete frame. + /// @param data is a pointer to the beginning of the ADTS frame. + /// @param num_bytes is the number of data bytes at @a data. + /// @return Size of the ADTS frame (header + payload) if successful, or + /// zero otherwise. + static size_t GetAdtsFrameSize(const uint8* data, size_t num_bytes); + + /// Get the size of the ADTS header from a partial or complete frame. + /// @param data is a pointer to the beginning of the ADTS frame. + /// @param num_bytes is the number of data bytes at @a data. + /// @return Size of the ADTS header if successful, or zero otherwise. + static size_t GetAdtsHeaderSize(const uint8* data, size_t num_bytes); + + /// Parse an ADTS header, extracting the fields within. + /// @param adts_frame is an input parameter pointing to the ADTS header + /// of an ADTS-framed audio sample. + /// @param adts_frame_size is the size, in bytes of the input ADTS frame. + /// @return true if successful, false otherwise. + bool Parse(const uint8* adts_frame, size_t adts_frame_size); + + /// Synthesize an AudioSpecificConfig record from the fields within the ADTS + /// header. + /// @param [out] buffer is a pointer to a vector to contain the + /// AudioSpecificConfig. + /// @return true if successful, false otherwise. + bool GetAudioSpecificConfig(std::vector* buffer) const; + + /// @return The audio profile for this ADTS frame. + uint8 GetObjectType() const; + + /// @return The sampling frequency for this ADTS frame. + uint32 GetSamplingFrequency() const; + + /// @return Number of channels for this AAC config. + uint8 GetNumChannels() const; + + private: + bool valid_config_; + uint8 profile_; + uint8 sampling_frequency_index_; + uint8 channel_configuration_; + + DISALLOW_COPY_AND_ASSIGN(AdtsHeader); +}; + +} // namespace mp2t +} // namespace media + +#endif // MEDIA_FORMATS_MP2T_ADTS_HEADER_H_ diff --git a/media/formats/mp2t/adts_header_unittest.cc b/media/formats/mp2t/adts_header_unittest.cc new file mode 100644 index 0000000000..c3c81bd63a --- /dev/null +++ b/media/formats/mp2t/adts_header_unittest.cc @@ -0,0 +1,91 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "media/formats/mp2t/adts_header.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kValidAdtsFrame[] = + "fff15080429ffcda004c61766335332e33352e30004258892a5361062403" + "d040000000001ff9055e9fe77ac56eb1677484e0ef0c3102a39daa8355a5" + "37ecab2b156e4ba73ceedb24ea51194e57c9385fa67eca8edc914902e852" + "3185b52299516e679fb3768aa9f13ccac5b257410080282c9a50318ec94e" + "ba24ea305bafab2b2beab16557ef9ecaa8f17bedea84788c8d42e4b3c65b" + "1e7ecae7528b909bc46c76cca73b906ec980ed9f32b25ecd28f43f9516de" + "3ff249f23bb9c93e64c4808195f284653c40592c1a8dc847f5f11791fd80" + "b18e02c1e1ed9f82c62a1f8ea0f5b6dbf2112c2202973b00de71bb49f906" + "ed1bc63768dda378c8f9c6ed1bb48f68dda378c9f68dda3768dda3768de3" + "23da3768de31bb492a5361062403d040000000001ff9055e9fe77ac56eb1" + "677484e0ef0c3102a39daa8355a537ecab2b156e4ba73ceedb24ea51194e" + "57c9385fa67eca8edc914902e8523185b52299516e679fb3768aa9f13cca" + "c5b257410080282c9a50318ec94eba24ea305bafab2b2beab16557ef9eca" + "a8f17bedea84788c8d42e4b3c65b1e7ecae7528b909bc46c76cca73b906e" + "c980ed9f32b25ecd28f43f9516de3ff249f23bb9c93e64c4808195f28465" + "3c40592c1a8dc847f5f11791fd80b18e02c1e1ed9f82c62a1f8ea0f5b6db" + "f2112c2202973b00de71bb49f906ed1bc63768dda378c8f9c6ed1bb48f68" + "dda378c9f68dda3768dda3768de323da3768de31bb4e"; + +const uint8 kExpectedAudioSpecificConfig[] = { 0x12, 0x10 }; + +} // anonymous namespace + +namespace media { +namespace mp2t { + +class AdtsHeaderTest : public testing::Test { + public: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(base::HexStringToBytes(kValidAdtsFrame, &adts_frame_)); + } + + protected: + std::vector adts_frame_; +}; + +TEST_F(AdtsHeaderTest, ParseSuccess) { + const uint8 kExpectedObjectType(2); + const uint32 kExpectedSamplingFrequency(44100); + const uint8 kExpectedNumChannels(2); + AdtsHeader adts_header; + EXPECT_TRUE(adts_header.Parse(adts_frame_.data(), adts_frame_.size())); + EXPECT_EQ(kExpectedObjectType, adts_header.GetObjectType()); + EXPECT_EQ(kExpectedSamplingFrequency, adts_header.GetSamplingFrequency()); + EXPECT_EQ(kExpectedNumChannels, adts_header.GetNumChannels()); + std::vector audio_specific_config; + ASSERT_TRUE(adts_header.GetAudioSpecificConfig(&audio_specific_config)); + EXPECT_EQ(arraysize(kExpectedAudioSpecificConfig), + audio_specific_config.size()); + EXPECT_EQ( + std::vector( + kExpectedAudioSpecificConfig, + kExpectedAudioSpecificConfig + arraysize(kExpectedAudioSpecificConfig)), + audio_specific_config); +} + +TEST_F(AdtsHeaderTest, ParseFailFrameSize) { + + AdtsHeader adts_header; + EXPECT_FALSE(adts_header.Parse(adts_frame_.data(), adts_frame_.size() - 1)); + EXPECT_FALSE(adts_header.Parse(adts_frame_.data(), adts_frame_.size() + 1)); + EXPECT_FALSE(adts_header.Parse(adts_frame_.data(), 1)); +} + +TEST_F(AdtsHeaderTest, GetFrameSizeSuccess) { + EXPECT_EQ(adts_frame_.size(), + AdtsHeader::GetAdtsFrameSize(adts_frame_.data(), + adts_frame_.size())); +} + +TEST_F(AdtsHeaderTest, GetHeaderSizeSuccess) { + const size_t kExpectedHeaderSize(7); + EXPECT_EQ(kExpectedHeaderSize, + AdtsHeader::GetAdtsHeaderSize(adts_frame_.data(), + adts_frame_.size())); +} + +} // Namespace mp2t +} // namespace media diff --git a/media/formats/mp2t/es_parser_adts.cc b/media/formats/mp2t/es_parser_adts.cc index 40fd83ba11..f3967b80da 100644 --- a/media/formats/mp2t/es_parser_adts.cc +++ b/media/formats/mp2t/es_parser_adts.cc @@ -13,26 +13,12 @@ #include "media/base/bit_reader.h" #include "media/base/media_sample.h" #include "media/base/timestamp.h" +#include "media/formats/mp2t/adts_header.h" #include "media/formats/mp2t/mp2t_common.h" #include "media/formats/mpeg/adts_constants.h" namespace media { -static int ExtractAdtsFrameSize(const uint8* adts_header) { - return ((static_cast(adts_header[5]) >> 5) | - (static_cast(adts_header[4]) << 3) | - ((static_cast(adts_header[3]) & 0x3) << 11)); -} - -static size_t ExtractAdtsFrequencyIndex(const uint8* adts_header) { - return ((adts_header[2] >> 2) & 0xf); -} - -static size_t ExtractAdtsChannelConfig(const uint8* adts_header) { - return (((adts_header[3] >> 6) & 0x3) | - ((adts_header[2] & 0x1) << 2)); -} - // Return true if buf corresponds to an ADTS syncword. // |buf| size must be at least 2. static bool isAdtsSyncWord(const uint8* buf) { @@ -72,7 +58,8 @@ static bool LookForSyncWord(const uint8* raw_es, int raw_es_size, // The layer field (2 bits) must be set to 0. continue; - int frame_size = ExtractAdtsFrameSize(cur_buf); + int frame_size = + mp2t::AdtsHeader::GetAdtsFrameSize(cur_buf, kAdtsHeaderMinSize); if (frame_size < kAdtsHeaderMinSize) { // Too short to be an ADTS frame. continue; @@ -131,21 +118,24 @@ bool EsParserAdts::Parse(const uint8* buf, int size, int64 pts, int64 dts) { int frame_size; while (LookForSyncWord(raw_es, raw_es_size, es_position, &es_position, &frame_size)) { + const uint8* frame_ptr = raw_es + es_position; DVLOG(LOG_LEVEL_ES) << "ADTS syncword @ pos=" << es_position << " frame_size=" << frame_size; DVLOG(LOG_LEVEL_ES) << "ADTS header: " - << base::HexEncode(&raw_es[es_position], kAdtsHeaderMinSize); + << base::HexEncode(frame_ptr, kAdtsHeaderMinSize); // Do not process the frame if this one is a partial frame. int remaining_size = raw_es_size - es_position; if (frame_size > remaining_size) break; + size_t header_size = AdtsHeader::GetAdtsHeaderSize(frame_ptr, frame_size); + // Update the audio configuration if needed. DCHECK_GE(frame_size, kAdtsHeaderMinSize); - if (!UpdateAudioConfiguration(&raw_es[es_position])) + if (!UpdateAudioConfiguration(frame_ptr, frame_size)) return false; // Get the PTS & the duration of this access unit. @@ -164,8 +154,8 @@ bool EsParserAdts::Parse(const uint8* buf, int size, int64 pts, int64 dts) { scoped_refptr sample = MediaSample::CopyFrom( - &raw_es[es_position], - frame_size, + frame_ptr + header_size, + frame_size - header_size, is_key_frame); sample->set_pts(current_pts); sample->set_dts(current_pts); @@ -194,37 +184,34 @@ void EsParserAdts::Reset() { last_audio_decoder_config_ = scoped_refptr(); } -bool EsParserAdts::UpdateAudioConfiguration(const uint8* adts_header) { +bool EsParserAdts::UpdateAudioConfiguration(const uint8* adts_frame, + size_t adts_frame_size) { + + const uint8 kAacSampleSizeBits(16); + + AdtsHeader adts_header; + if (!adts_header.Parse(adts_frame, adts_frame_size)) { + LOG(ERROR) << "Error parsing ADTS frame header."; + return false; + } + std::vector audio_specific_config; + if (!adts_header.GetAudioSpecificConfig(&audio_specific_config)) + return false; + if (last_audio_decoder_config_) { - // Varying audio configurations currently not supported. Just assume that - // the audio configuration has not changed. - return true; - } - - size_t frequency_index = ExtractAdtsFrequencyIndex(adts_header); - if (frequency_index >= kAdtsFrequencyTableSize) { - // Frequency index 13 & 14 are reserved - // while 15 means that the frequency is explicitly written - // (not supported). + // Verify that the audio decoder config has not changed. + if (last_audio_decoder_config_->extra_data() == audio_specific_config) { + // Audio configuration has not changed. + return true; + } + NOTIMPLEMENTED() << "Varying audio configurations are not supported."; return false; } - size_t channel_configuration = ExtractAdtsChannelConfig(adts_header); - if (channel_configuration == 0 || - channel_configuration >= kAdtsNumChannelsTableSize) { - // TODO(damienv): Add support for inband channel configuration. - return false; - } - - // TODO(damienv): support HE-AAC frequency doubling (SBR) - // based on the incoming ADTS profile. - int samples_per_second = kAdtsFrequencyTable[frequency_index]; - int adts_profile = (adts_header[2] >> 6) & 0x3; - // The following code is written according to ISO 14496 Part 3 Table 1.11 and // Table 1.22. (Table 1.11 refers to the capping to 48000, Table 1.22 refers // to SBR doubling the AAC sample rate.) - // TODO(damienv) : Extend sample rate cap to 96kHz for Level 5 content. + int samples_per_second = adts_header.GetSamplingFrequency(); int extended_samples_per_second = sbr_in_mimetype_ ? std::min(2 * samples_per_second, 48000) : samples_per_second; @@ -235,19 +222,20 @@ bool EsParserAdts::UpdateAudioConfiguration(const uint8* adts_header) { kMpeg2Timescale, kInfiniteDuration, kCodecAAC, - std::string(), // TODO(tinskip): calculate codec string. + AudioStreamInfo::GetCodecString(kCodecAAC, + adts_header.GetObjectType()), std::string(), - 16, - kAdtsNumChannelsTable[channel_configuration], - samples_per_second, - NULL, // TODO(tinskip): calculate AudioSpecificConfig. - 0, + kAacSampleSizeBits, + adts_header.GetNumChannels(), + extended_samples_per_second, + audio_specific_config.data(), + audio_specific_config.size(), false)); DVLOG(1) << "Sampling frequency: " << samples_per_second; DVLOG(1) << "Extended sampling frequency: " << extended_samples_per_second; - DVLOG(1) << "Channel config: " << channel_configuration; - DVLOG(1) << "Adts profile: " << adts_profile; + DVLOG(1) << "Channel config: " << adts_header.GetNumChannels(); + DVLOG(1) << "Object type: " << adts_header.GetObjectType(); // Reset the timestamp helper to use a new sampling frequency. if (audio_timestamp_helper_) { int64 base_timestamp = audio_timestamp_helper_->GetTimestamp(); @@ -256,7 +244,7 @@ bool EsParserAdts::UpdateAudioConfiguration(const uint8* adts_header) { audio_timestamp_helper_->SetBaseTimestamp(base_timestamp); } else { audio_timestamp_helper_.reset( - new AudioTimestampHelper(kMpeg2Timescale, samples_per_second)); + new AudioTimestampHelper(kMpeg2Timescale, extended_samples_per_second)); } // Audio config notification. diff --git a/media/formats/mp2t/es_parser_adts.h b/media/formats/mp2t/es_parser_adts.h index 9c5da7e06f..062ad4ef22 100644 --- a/media/formats/mp2t/es_parser_adts.h +++ b/media/formats/mp2t/es_parser_adts.h @@ -46,7 +46,7 @@ class EsParserAdts : public EsParser { // Signal any audio configuration change (if any). // Return false if the current audio config is not // a supported ADTS audio config. - bool UpdateAudioConfiguration(const uint8* adts_header); + bool UpdateAudioConfiguration(const uint8* adts_frame, size_t frame_size); // Discard some bytes from the ES stream. void DiscardEs(int nbytes); diff --git a/media/formats/mp2t/mp2t.gyp b/media/formats/mp2t/mp2t.gyp index abb11c802f..373029b160 100644 --- a/media/formats/mp2t/mp2t.gyp +++ b/media/formats/mp2t/mp2t.gyp @@ -19,6 +19,8 @@ 'target_name': 'mp2t', 'type': '<(component)', 'sources': [ + 'adts_header.cc', + 'adts_header.h', 'es_parser.h', 'es_parser_adts.cc', 'es_parser_adts.h', @@ -45,6 +47,7 @@ 'target_name': 'mp2t_unittest', 'type': '<(gtest_target_type)', 'sources': [ + 'adts_header_unittest.cc', 'es_parser_h264_unittest.cc', 'mp2t_media_parser_unittest.cc', ], diff --git a/packager.gyp b/packager.gyp index 3a6100adea..db3596010b 100644 --- a/packager.gyp +++ b/packager.gyp @@ -27,7 +27,10 @@ 'dependencies': [ 'media/event/media_event.gyp:media_event', 'media/file/file.gyp:file', + 'media/filters/filters.gyp:filters', + 'media/formats/mp2t/mp2t.gyp:mp2t', 'media/formats/mp4/mp4.gyp:mp4', + 'media/formats/mpeg/mpeg.gyp:mpeg', 'third_party/gflags/gflags.gyp:gflags', ], }, @@ -52,7 +55,10 @@ ], 'dependencies': [ 'media/file/file.gyp:file', + 'media/filters/filters.gyp:filters', + 'media/formats/mp2t/mp2t.gyp:mp2t', 'media/formats/mp4/mp4.gyp:mp4', + 'media/formats/mpeg/mpeg.gyp:mpeg', 'media/test/media_test.gyp:media_test_support', 'testing/gtest.gyp:gtest', ], @@ -64,6 +70,7 @@ 'media/base/media_base.gyp:*', 'media/event/media_event.gyp:*', 'media/file/file.gyp:*', + 'media/formats/mp2t/mp2t.gyp:*', 'media/formats/mp4/mp4.gyp:*', 'mpd/mpd.gyp:*', ],