diff --git a/packager/media/filters/nal_unit_to_byte_stream_converter.h b/packager/media/filters/nal_unit_to_byte_stream_converter.h index ada87aea96..7fbf4c7b7d 100644 --- a/packager/media/filters/nal_unit_to_byte_stream_converter.h +++ b/packager/media/filters/nal_unit_to_byte_stream_converter.h @@ -18,10 +18,11 @@ namespace media { class VideoStreamInfo; +// Methods are virtual for mocking. class NalUnitToByteStreamConverter { public: NalUnitToByteStreamConverter(); - ~NalUnitToByteStreamConverter(); + virtual ~NalUnitToByteStreamConverter(); /// This must be called before calling other methods. /// @param decoder_configuration_data is the pointer to a decoder config data. @@ -31,9 +32,9 @@ class NalUnitToByteStreamConverter { /// passed to ConvertUnitToByteStream() should be escaped with /// emulation prevention byte. /// @return true on success, false otherwise. - bool Initialize(const uint8_t* decoder_configuration_data, - size_t decoder_configuration_data_size, - bool escape_data); + virtual bool Initialize(const uint8_t* decoder_configuration_data, + size_t decoder_configuration_data_size, + bool escape_data); /// Converts unit stream to byte stream using the data passed to Initialize(). /// The method will function correctly even if @a sample is encrypted using @@ -42,9 +43,10 @@ class NalUnitToByteStreamConverter { /// @param sample_size is the size of @a sample. /// @param output is set to the the converted sample, on success. /// @return true on success, false otherwise. - bool ConvertUnitToByteStream(const uint8_t* sample, size_t sample_size, - bool is_key_frame, - std::vector* output); + virtual bool ConvertUnitToByteStream(const uint8_t* sample, + size_t sample_size, + bool is_key_frame, + std::vector* output); private: friend class NalUnitToByteStreamConverterTest; diff --git a/packager/media/formats/mp2t/mp2t.gyp b/packager/media/formats/mp2t/mp2t.gyp index 137c40bb46..0935d53347 100644 --- a/packager/media/formats/mp2t/mp2t.gyp +++ b/packager/media/formats/mp2t/mp2t.gyp @@ -15,13 +15,17 @@ 'sources': [ 'adts_header.cc', 'adts_header.h', - 'es_parser.h', 'es_parser_adts.cc', 'es_parser_adts.h', 'es_parser_h264.cc', 'es_parser_h264.h', + 'es_parser.h', 'mp2t_media_parser.cc', 'mp2t_media_parser.h', + 'pes_packet_generator.cc', + 'pes_packet_generator.h', + 'pes_packet.cc', + 'pes_packet.h', 'ts_packet.cc', 'ts_packet.h', 'ts_section_pat.cc', @@ -35,6 +39,9 @@ ], 'dependencies': [ '../../base/media_base.gyp:media_base', + # TODO(rkuroiwa): AACAudioSpecificConfig is used to create ADTS. + # Break this dependency on mp4 by moving it to media/filters. + '../../formats/mp4/mp4.gyp:mp4', ], }, { @@ -44,6 +51,7 @@ 'adts_header_unittest.cc', 'es_parser_h264_unittest.cc', 'mp2t_media_parser_unittest.cc', + 'pes_packet_generator_unittest.cc', ], 'dependencies': [ '../../../testing/gtest.gyp:gtest', diff --git a/packager/media/formats/mp2t/pes_packet.cc b/packager/media/formats/mp2t/pes_packet.cc new file mode 100644 index 0000000000..810b083ec5 --- /dev/null +++ b/packager/media/formats/mp2t/pes_packet.cc @@ -0,0 +1,18 @@ +// 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/formats/mp2t/pes_packet.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +PesPacket::PesPacket() {} +PesPacket::~PesPacket() {} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/pes_packet.h b/packager/media/formats/mp2t/pes_packet.h new file mode 100644 index 0000000000..05ed7fd95b --- /dev/null +++ b/packager/media/formats/mp2t/pes_packet.h @@ -0,0 +1,79 @@ +// 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 + +#ifndef PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_H_ +#define PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_H_ + +#include +#include + +#include "packager/base/macros.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +/// Class that carries PES packet information. +class PesPacket { + public: + PesPacket(); + ~PesPacket(); + + /// @return the stream ID of the data that's carried by the PES packete. + uint8_t stream_id() const { return stream_id_; } + /// @param stream_id is used to set the stream ID. + void set_stream_id(uint8_t stream_id) { stream_id_ = stream_id; } + + /// @return true if dts has been set. + bool has_dts() const { return dts_ < 0; } + /// @return true if pts has been set. + bool has_pts() const { return pts_ < 0; } + + /// @return dts. + int64_t dts() const { return dts_; } + /// @param dts is the dts for this PES packet. + void set_dts(int64_t dts) { + dts_ = dts; + } + + /// @return pts. + int64_t pts() const { return pts_; } + /// @param pts is the pts for this PES packet. + void set_pts(int64_t pts) { + pts_ = pts; + } + + /// Duration is not really part of PES but its here to calculate stream's + /// duration. + /// @return duration of this PES in timescale. + int64_t duration() const { return duration_; } + /// @param duration of this PES. + void set_duration(int64_t duration) { duration_ = duration; } + + /// @return data carried by this PES, the payload. + const std::vector& data() const { return data_; } + /// @return mutable data for this PES. + std::vector* mutable_data() { return &data_; } + + private: + uint8_t stream_id_ = 0; + + // These values mean "not set" when the value is less than 0. + int64_t dts_ = -1; + int64_t pts_ = -1; + + int64_t duration_ = 0; + + std::vector data_; + + DISALLOW_COPY_AND_ASSIGN(PesPacket); +}; + +} // namespace mp2t +} // namespace media +} // namespace edash_packager + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_H_ diff --git a/packager/media/formats/mp2t/pes_packet_generator.cc b/packager/media/formats/mp2t/pes_packet_generator.cc new file mode 100644 index 0000000000..bd0fb03328 --- /dev/null +++ b/packager/media/formats/mp2t/pes_packet_generator.cc @@ -0,0 +1,115 @@ +// 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/formats/mp2t/pes_packet_generator.h" + +#include "packager/media/base/audio_stream_info.h" +#include "packager/media/base/media_sample.h" +#include "packager/media/base/video_stream_info.h" +#include "packager/media/filters/nal_unit_to_byte_stream_converter.h" +#include "packager/media/formats/mp2t/pes_packet.h" +#include "packager/media/formats/mp4/aac_audio_specific_config.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +namespace { +const bool kEscapeData = true; +const uint8_t kVideoStreamId = 0xe0; +const uint8_t kAudioStreamId = 0xc0; +} // namespace + +PesPacketGenerator::PesPacketGenerator() {} +PesPacketGenerator::~PesPacketGenerator() {} + +bool PesPacketGenerator::Initialize(const StreamInfo& stream_info) { + pes_packets_.clear(); + stream_type_ = stream_info.stream_type(); + + if (stream_type_ == kStreamVideo) { + const VideoStreamInfo& video_stream_info = + static_cast(stream_info); + if (video_stream_info.codec() != VideoCodec::kCodecH264) { + NOTIMPLEMENTED() << "Video codec " << video_stream_info.codec() + << " is not supported."; + return false; + } + timescale_scale_ = 90000.0 / video_stream_info.time_scale(); + converter_.reset(new NalUnitToByteStreamConverter()); + return converter_->Initialize(video_stream_info.extra_data().data(), + video_stream_info.extra_data().size(), + !kEscapeData); + } else if (stream_type_ == kStreamAudio) { + const AudioStreamInfo& audio_stream_info = + static_cast(stream_info); + if (audio_stream_info.codec() != AudioCodec::kCodecAAC) { + NOTIMPLEMENTED() << "Audio codec " << audio_stream_info.codec() + << " is not supported yet."; + return false; + } + timescale_scale_ = 90000.0 / audio_stream_info.time_scale(); + adts_converter_.reset(new mp4::AACAudioSpecificConfig()); + return adts_converter_->Parse(audio_stream_info.extra_data()); + } + + NOTIMPLEMENTED() << "Stream type: " << stream_type_ << " not implemented."; + return false; +} + +bool PesPacketGenerator::PushSample(scoped_refptr sample) { + if (!current_processing_pes_) + current_processing_pes_.reset(new PesPacket()); + + current_processing_pes_->set_duration(sample->duration()); + current_processing_pes_->set_pts(timescale_scale_ * sample->pts()); + current_processing_pes_->set_dts(timescale_scale_ * sample->dts()); + if (stream_type_ == kStreamVideo) { + DCHECK(converter_); + if (!converter_->ConvertUnitToByteStream( + sample->data(), sample->data_size(), sample->is_key_frame(), + current_processing_pes_->mutable_data())) { + LOG(ERROR) << "Failed to convert sample to byte stream."; + return false; + } + current_processing_pes_->set_stream_id(kVideoStreamId); + pes_packets_.push_back(current_processing_pes_.Pass()); + return true; + } + DCHECK_EQ(stream_type_, kStreamAudio); + DCHECK(adts_converter_); + + std::vector aac_frame(sample->data(), + sample->data() + sample->data_size()); + if (!adts_converter_->ConvertToADTS(&aac_frame)) + return false; + + // TODO(rkuriowa): Put multiple samples in the PES packet to reduce # of PES + // packets. + current_processing_pes_->mutable_data()->swap(aac_frame); + current_processing_pes_->set_stream_id(kAudioStreamId); + pes_packets_.push_back(current_processing_pes_.Pass()); + return true; +} + +size_t PesPacketGenerator::NumberOfReadyPesPackets() { + return pes_packets_.size(); +} + +scoped_ptr PesPacketGenerator::GetNextPesPacket() { + DCHECK(!pes_packets_.empty()); + scoped_ptr pes = pes_packets_.front().Pass(); + pes_packets_.pop_front(); + return pes.Pass(); +} + +bool PesPacketGenerator::Flush() { + return true; +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/pes_packet_generator.h b/packager/media/formats/mp2t/pes_packet_generator.h new file mode 100644 index 0000000000..00b673e560 --- /dev/null +++ b/packager/media/formats/mp2t/pes_packet_generator.h @@ -0,0 +1,89 @@ +// 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 + +#ifndef PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_GENERATOR_H_ +#define PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_GENERATOR_H_ + +#include + +#include "packager/base/memory/scoped_ptr.h" +#include "packager/media/base/media_sample.h" +#include "packager/media/base/stream_info.h" + +namespace edash_packager { +namespace media { + +class NalUnitToByteStreamConverter; +class StreamInfo; + +namespace mp4 { +class AACAudioSpecificConfig; +} // namespace mp4 + +namespace mp2t { + +class PesPacket; + +/// Generates PesPackets from MediaSamples. +class PesPacketGenerator { + public: + PesPacketGenerator(); + ~PesPacketGenerator(); + + /// Initialize the object. This clears the internal state first so any + /// PesPackets that have not been flushed will be lost. + /// @param stream is the stream info for the elementary stream that will be + /// added via PushSample(). + /// @return true on success, false otherwise. + bool Initialize(const StreamInfo& stream); + + /// Add a sample to the generator. This does not necessarily increase + /// NumberOfReadyPesPackets(). + /// If this returns false, the object may end up in an undefined state. + /// @return true on success, false otherwise. + bool PushSample(scoped_refptr sample); + + /// @return The number of PES packets that are ready to be consumed. + size_t NumberOfReadyPesPackets(); + + /// Removes the next PES packet from the stream and returns it. Must have at + /// least one packet ready. + /// @return Next PES packet that is ready. + scoped_ptr GetNextPesPacket(); + + /// Flush the object. This may create more PesPackets with the stored + /// samples. + /// It is safe to call NumberOfReadyPesPackets() and GetNextPesPacket() after + /// this. + /// @return true on success, false otherwise. + bool Flush(); + + private: + friend class PesPacketGeneratorTest; + + StreamType stream_type_; + + // Calculated by 90000 / input stream's timescale. This is used to scale the + // timestamps. + double timescale_scale_ = 0.0; + + scoped_ptr converter_; + scoped_ptr adts_converter_; + + // This is the PES packet that this object is currently working on. + // This can be used to create a PES from multiple audio samples. + scoped_ptr current_processing_pes_; + + std::list> pes_packets_; + + DISALLOW_COPY_AND_ASSIGN(PesPacketGenerator); +}; + +} // namespace mp2t +} // namespace media +} // namespace edash_packager + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_GENERATOR_H_ diff --git a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc new file mode 100644 index 0000000000..c9b90b887e --- /dev/null +++ b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc @@ -0,0 +1,323 @@ +// 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 +#include + +#include "packager/media/base/audio_stream_info.h" +#include "packager/media/base/media_sample.h" +#include "packager/media/base/text_stream_info.h" +#include "packager/media/base/video_stream_info.h" +#include "packager/media/filters/nal_unit_to_byte_stream_converter.h" +#include "packager/media/formats/mp2t/pes_packet.h" +#include "packager/media/formats/mp2t/pes_packet_generator.h" +#include "packager/media/formats/mp4/aac_audio_specific_config.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::SetArgPointee; +using ::testing::Return; + +namespace { + +// Bogus data for testing. +const uint8_t kAnyData[] = { + 0x56, 0x87, 0x88, 0x33, 0x98, 0xAF, 0xE5, +}; + +const bool kIsKeyFrame = true; + +// Only {Audio,Video}Codec and extra data matter for this test. Other values are +// bogus. +const VideoCodec kH264VideoCodec = VideoCodec::kCodecH264; +const AudioCodec kAacAudioCodec = AudioCodec::kCodecAAC; + +// TODO(rkuroiwa): It might make sense to inject factory functions to create +// NalUnitToByteStreamConverter and AACAudioSpecificConfig so that these +// extra data don't need to be copy pasted from other tests. +const uint8_t kVideoExtraData[] = { + 0x01, // configuration version (must be 1) + 0x00, // AVCProfileIndication (bogus) + 0x00, // profile_compatibility (bogus) + 0x00, // AVCLevelIndication (bogus) + 0xFF, // Length size minus 1 == 3 + 0xE1, // 1 sps. + 0x00, 0x1D, // SPS length == 29 + 0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4, + 0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91, + 0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA, + 0x60, 0x0F, 0x16, 0x2D, 0x96, + 0x01, // 1 pps. + 0x00, 0x0A, // PPS length == 10 + 0x68, 0xFE, 0xFD, 0xFC, 0xFB, 0x11, 0x12, 0x13, 0x14, 0x15, +}; + +// Basic profile. +const uint8_t kAudioExtraData[] = {0x12, 0x10}; + +const int kTrackId = 0; +const uint32_t kTimeScale = 90000; +const uint64_t kDuration = 180000; +const char kCodecString[] = "avc1"; +const char kLanguage[] = "eng"; +const uint32_t kWidth = 1280; +const uint32_t kHeight = 720; +const uint32_t kPixelWidth = 1; +const uint32_t kPixelHeight = 1; +const uint16_t kTrickPlayRate = 1; +const uint8_t kNaluLengthSize = 1; +const bool kIsEncrypted = false; + +const uint8_t kSampleBits = 16; +const uint8_t kNumChannels = 2; +const uint32_t kSamplingFrequency = 44100; +const uint32_t kMaxBitrate = 320000; +const uint32_t kAverageBitrate = 256000; + +class MockNalUnitToByteStreamConverter : public NalUnitToByteStreamConverter { + public: + MOCK_METHOD3(Initialize, + bool(const uint8_t* decoder_configuration_data, + size_t decoder_configuration_data_size, + bool escape_data)); + MOCK_METHOD4(ConvertUnitToByteStream, + bool(const uint8_t* sample, + size_t sample_size, + bool is_key_frame, + std::vector* output)); +}; + +class MockAACAudioSpecificConfig : public mp4::AACAudioSpecificConfig { + public: + MOCK_METHOD1(Parse, bool(const std::vector& data)); + MOCK_CONST_METHOD1(ConvertToADTS, bool(std::vector* buffer)); +}; + +scoped_refptr CreateVideoStreamInfo(VideoCodec codec) { + scoped_refptr stream_info(new VideoStreamInfo( + kTrackId, kTimeScale, kDuration, codec, kCodecString, kLanguage, + kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, + kNaluLengthSize, kVideoExtraData, arraysize(kVideoExtraData), + kIsEncrypted)); + return stream_info; +} + +scoped_refptr CreateAudioStreamInfo(AudioCodec codec) { + scoped_refptr stream_info(new AudioStreamInfo( + kTrackId, kTimeScale, kDuration, codec, kCodecString, + kLanguage, kSampleBits, kNumChannels, kSamplingFrequency, kMaxBitrate, + kAverageBitrate, kAudioExtraData, arraysize(kAudioExtraData), + kIsEncrypted)); + return stream_info; +} + +} // namespace + +class PesPacketGeneratorTest : public ::testing::Test { + protected: + void UseMockNalUnitToByteStreamConverter( + scoped_ptr + mock_nal_unit_to_byte_stream_converter) { + generator_.converter_ = mock_nal_unit_to_byte_stream_converter.Pass(); + } + + void UseMockAACAudioSpecificConfig( + scoped_ptr mock) { + generator_.adts_converter_ = mock.Pass(); + } + + PesPacketGenerator generator_; +}; + +TEST_F(PesPacketGeneratorTest, InitializeVideo) { + scoped_refptr stream_info( + CreateVideoStreamInfo(kH264VideoCodec)); + EXPECT_TRUE(generator_.Initialize(*stream_info)); +} + +TEST_F(PesPacketGeneratorTest, InitializeVideoNonH264) { + scoped_refptr stream_info( + CreateVideoStreamInfo(VideoCodec::kCodecVP9)); + EXPECT_FALSE(generator_.Initialize(*stream_info)); +} + +TEST_F(PesPacketGeneratorTest, InitializeAudio) { + scoped_refptr stream_info( + CreateAudioStreamInfo(kAacAudioCodec)); + EXPECT_TRUE(generator_.Initialize(*stream_info)); +} + +TEST_F(PesPacketGeneratorTest, InitializeAudioNonAac) { + scoped_refptr stream_info( + CreateAudioStreamInfo(AudioCodec::kCodecOpus)); + EXPECT_FALSE(generator_.Initialize(*stream_info)); +} + +// Text is not supported yet. +TEST_F(PesPacketGeneratorTest, InitializeTextInfo) { + scoped_refptr stream_info( + new TextStreamInfo(kTrackId, kTimeScale, kDuration, kCodecString, + kLanguage, std::string(), kWidth, kHeight)); + EXPECT_FALSE(generator_.Initialize(*stream_info)); +} + +TEST_F(PesPacketGeneratorTest, AddVideoSample) { + scoped_refptr stream_info( + CreateVideoStreamInfo(kH264VideoCodec)); + EXPECT_TRUE(generator_.Initialize(*stream_info)); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + + scoped_refptr sample = + MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); + const uint32_t kPts = 12345; + const uint32_t kDts = 12300; + sample->set_pts(kPts); + sample->set_dts(kDts); + + std::vector expected_data(kAnyData, kAnyData + arraysize(kAnyData)); + + scoped_ptr mock( + new MockNalUnitToByteStreamConverter()); + EXPECT_CALL(*mock, + ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _)) + .WillOnce(DoAll(SetArgPointee<3>(expected_data), Return(true))); + + UseMockNalUnitToByteStreamConverter(mock.Pass()); + + EXPECT_TRUE(generator_.PushSample(sample)); + EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets()); + scoped_ptr pes_packet = generator_.GetNextPesPacket(); + ASSERT_TRUE(pes_packet); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + + EXPECT_EQ(0xe0, pes_packet->stream_id()); + EXPECT_EQ(kPts, pes_packet->pts()); + EXPECT_EQ(kDts, pes_packet->dts()); + EXPECT_EQ(expected_data, pes_packet->data()); + + EXPECT_TRUE(generator_.Flush()); +} + +TEST_F(PesPacketGeneratorTest, AddVideoSampleFailedToConvert) { + scoped_refptr stream_info( + CreateVideoStreamInfo(kH264VideoCodec)); + EXPECT_TRUE(generator_.Initialize(*stream_info)); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + + scoped_refptr sample = + MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); + + std::vector expected_data(kAnyData, kAnyData + arraysize(kAnyData)); + scoped_ptr mock( + new MockNalUnitToByteStreamConverter()); + EXPECT_CALL(*mock, + ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _)) + .WillOnce(Return(false)); + + UseMockNalUnitToByteStreamConverter(mock.Pass()); + + EXPECT_FALSE(generator_.PushSample(sample)); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + EXPECT_TRUE(generator_.Flush()); +} + +TEST_F(PesPacketGeneratorTest, AddAudioSample) { + scoped_refptr stream_info( + CreateAudioStreamInfo(kAacAudioCodec)); + EXPECT_TRUE(generator_.Initialize(*stream_info)); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + + scoped_refptr sample = + MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); + + std::vector expected_data(kAnyData, kAnyData + arraysize(kAnyData)); + + scoped_ptr mock(new MockAACAudioSpecificConfig()); + EXPECT_CALL(*mock, ConvertToADTS(_)) + .WillOnce(DoAll(SetArgPointee<0>(expected_data), Return(true))); + + UseMockAACAudioSpecificConfig(mock.Pass()); + + EXPECT_TRUE(generator_.PushSample(sample)); + EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets()); + scoped_ptr pes_packet = generator_.GetNextPesPacket(); + ASSERT_TRUE(pes_packet); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + + EXPECT_EQ(0xc0, pes_packet->stream_id()); + EXPECT_EQ(expected_data, pes_packet->data()); + + EXPECT_TRUE(generator_.Flush()); +} + +TEST_F(PesPacketGeneratorTest, AddAudioSampleFailedToConvert) { + scoped_refptr stream_info( + CreateAudioStreamInfo(kAacAudioCodec)); + EXPECT_TRUE(generator_.Initialize(*stream_info)); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + + scoped_refptr sample = + MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); + + scoped_ptr mock(new MockAACAudioSpecificConfig()); + EXPECT_CALL(*mock, ConvertToADTS(_)).WillOnce(Return(false)); + + UseMockAACAudioSpecificConfig(mock.Pass()); + + EXPECT_FALSE(generator_.PushSample(sample)); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + EXPECT_TRUE(generator_.Flush()); +} + +// Because TS has to use 90000 as its timescale, make sure that the timestamps +// are scaled. +TEST_F(PesPacketGeneratorTest, TimeStampScaling) { + const uint32_t kTestTimescale = 1000; + scoped_refptr stream_info(new VideoStreamInfo( + kTrackId, kTestTimescale, kDuration, kH264VideoCodec, kCodecString, + kLanguage, kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, + kNaluLengthSize, kVideoExtraData, arraysize(kVideoExtraData), + kIsEncrypted)); + EXPECT_TRUE(generator_.Initialize(*stream_info)); + + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + + scoped_refptr sample = + MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); + const uint32_t kPts = 5000; + const uint32_t kDts = 4000; + sample->set_pts(kPts); + sample->set_dts(kDts); + + scoped_ptr mock( + new MockNalUnitToByteStreamConverter()); + EXPECT_CALL(*mock, + ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _)) + .WillOnce(Return(true)); + + UseMockNalUnitToByteStreamConverter(mock.Pass()); + + EXPECT_TRUE(generator_.PushSample(sample)); + EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets()); + scoped_ptr pes_packet = generator_.GetNextPesPacket(); + ASSERT_TRUE(pes_packet); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + + // Since 90000 (MPEG2 timescale) / 1000 (input timescale) is 90, the + // timestamps should be multipled by 90. + EXPECT_EQ(kPts * 90, pes_packet->pts()); + EXPECT_EQ(kDts * 90, pes_packet->dts()); + + EXPECT_TRUE(generator_.Flush()); +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp4/aac_audio_specific_config.cc b/packager/media/formats/mp4/aac_audio_specific_config.cc index 489339011f..f6a9a2df92 100644 --- a/packager/media/formats/mp4/aac_audio_specific_config.cc +++ b/packager/media/formats/mp4/aac_audio_specific_config.cc @@ -127,6 +127,31 @@ bool AACAudioSpecificConfig::Parse(const std::vector& data) { channel_config_ <= 7; } +bool AACAudioSpecificConfig::ConvertToADTS(std::vector* buffer) const { + size_t size = buffer->size() + kADTSHeaderSize; + + DCHECK(audio_object_type_ >= 1 && audio_object_type_ <= 4 && + frequency_index_ != 0xf && channel_config_ <= 7); + + // ADTS header uses 13 bits for packet size. + if (size >= (1 << 13)) + return false; + + std::vector& adts = *buffer; + + adts.insert(buffer->begin(), kADTSHeaderSize, 0); + adts[0] = 0xff; + adts[1] = 0xf1; + adts[2] = ((audio_object_type_ - 1) << 6) + (frequency_index_ << 2) + + (channel_config_ >> 2); + adts[3] = ((channel_config_ & 0x3) << 6) + (size >> 11); + adts[4] = (size & 0x7ff) >> 3; + adts[5] = ((size & 7) << 5) + 0x1f; + adts[6] = 0xfc; + + return true; +} + uint32_t AACAudioSpecificConfig::GetOutputSamplesPerSecond( bool sbr_in_mimetype) const { if (extension_frequency_ > 0) @@ -156,31 +181,6 @@ uint8_t AACAudioSpecificConfig::GetNumChannels(bool sbr_in_mimetype) const { return num_channels_; } -bool AACAudioSpecificConfig::ConvertToADTS(std::vector* buffer) const { - size_t size = buffer->size() + kADTSHeaderSize; - - DCHECK(audio_object_type_ >= 1 && audio_object_type_ <= 4 && - frequency_index_ != 0xf && channel_config_ <= 7); - - // ADTS header uses 13 bits for packet size. - if (size >= (1 << 13)) - return false; - - std::vector& adts = *buffer; - - adts.insert(buffer->begin(), kADTSHeaderSize, 0); - adts[0] = 0xff; - adts[1] = 0xf1; - adts[2] = ((audio_object_type_ - 1) << 6) + (frequency_index_ << 2) + - (channel_config_ >> 2); - adts[3] = ((channel_config_ & 0x3) << 6) + (size >> 11); - adts[4] = (size & 0x7ff) >> 3; - adts[5] = ((size & 7) << 5) + 0x1f; - adts[6] = 0xfc; - - return true; -} - // Currently this function only support GASpecificConfig defined in // ISO 14496 Part 3 Table 4.1 - Syntax of GASpecificConfig() bool AACAudioSpecificConfig::SkipDecoderGASpecificConfig(BitReader* bit_reader) const { diff --git a/packager/media/formats/mp4/aac_audio_specific_config.h b/packager/media/formats/mp4/aac_audio_specific_config.h index 7da98d3392..d40f061d25 100644 --- a/packager/media/formats/mp4/aac_audio_specific_config.h +++ b/packager/media/formats/mp4/aac_audio_specific_config.h @@ -17,6 +17,7 @@ class BitReader; namespace mp4 { +// Methods are virtual for mocking. /// This class parses the AAC information from decoder specific information /// embedded in the @b esds box in an ISO BMFF file. /// Please refer to ISO 14496 Part 3 Table 1.13 - Syntax of AudioSpecificConfig @@ -24,7 +25,7 @@ namespace mp4 { class AACAudioSpecificConfig { public: AACAudioSpecificConfig(); - ~AACAudioSpecificConfig(); + virtual ~AACAudioSpecificConfig(); /// Parse the AAC config from decoder specific information embedded in an @b /// esds box. The function will parse the data and get the @@ -32,7 +33,14 @@ class AACAudioSpecificConfig { /// ElementaryStreamDescriptor to get audio stream configurations. /// @param data contains decoder specific information from an @b esds box. /// @return true if successful, false otherwise. - bool Parse(const std::vector& data); + virtual bool Parse(const std::vector& data); + + /// Convert a raw AAC frame into an AAC frame with an ADTS header. + /// @param[in,out] buffer contains the raw AAC frame on input, and the + /// converted frame on output if successful; it is untouched + /// on failure. + /// @return true on success, false otherwise. + virtual bool ConvertToADTS(std::vector* buffer) const; /// @param sbr_in_mimetype indicates whether SBR mode is specified in the /// mimetype, i.e. codecs parameter contains mp4a.40.5. @@ -44,13 +52,6 @@ class AACAudioSpecificConfig { /// @return Number of channels for the AAC stream. uint8_t GetNumChannels(bool sbr_in_mimetype) const; - /// Convert a raw AAC frame into an AAC frame with an ADTS header. - /// @param[in,out] buffer contains the raw AAC frame on input, and the - /// converted frame on output if successful; it is untouched - /// on failure. - /// @return true on success, false otherwise. - bool ConvertToADTS(std::vector* buffer) const; - /// @return The audio object type for this AAC config. uint8_t audio_object_type() const { return audio_object_type_; }