Add PES packet related classes

- Define PesPacket class.
- PesPacketGenerator creates PesPackets from samples.

Change-Id: Icfd3656b498e0075f83ff3c789f95658f98c6144
This commit is contained in:
Rintaro Kuroiwa 2016-03-16 12:10:12 -07:00
parent 40e1cc87d1
commit cfd782ff40
9 changed files with 677 additions and 42 deletions

View File

@ -18,10 +18,11 @@ namespace media {
class VideoStreamInfo; class VideoStreamInfo;
// Methods are virtual for mocking.
class NalUnitToByteStreamConverter { class NalUnitToByteStreamConverter {
public: public:
NalUnitToByteStreamConverter(); NalUnitToByteStreamConverter();
~NalUnitToByteStreamConverter(); virtual ~NalUnitToByteStreamConverter();
/// This must be called before calling other methods. /// This must be called before calling other methods.
/// @param decoder_configuration_data is the pointer to a decoder config data. /// @param decoder_configuration_data is the pointer to a decoder config data.
@ -31,7 +32,7 @@ class NalUnitToByteStreamConverter {
/// passed to ConvertUnitToByteStream() should be escaped with /// passed to ConvertUnitToByteStream() should be escaped with
/// emulation prevention byte. /// emulation prevention byte.
/// @return true on success, false otherwise. /// @return true on success, false otherwise.
bool Initialize(const uint8_t* decoder_configuration_data, virtual bool Initialize(const uint8_t* decoder_configuration_data,
size_t decoder_configuration_data_size, size_t decoder_configuration_data_size,
bool escape_data); bool escape_data);
@ -42,7 +43,8 @@ class NalUnitToByteStreamConverter {
/// @param sample_size is the size of @a sample. /// @param sample_size is the size of @a sample.
/// @param output is set to the the converted sample, on success. /// @param output is set to the the converted sample, on success.
/// @return true on success, false otherwise. /// @return true on success, false otherwise.
bool ConvertUnitToByteStream(const uint8_t* sample, size_t sample_size, virtual bool ConvertUnitToByteStream(const uint8_t* sample,
size_t sample_size,
bool is_key_frame, bool is_key_frame,
std::vector<uint8_t>* output); std::vector<uint8_t>* output);

View File

@ -15,13 +15,17 @@
'sources': [ 'sources': [
'adts_header.cc', 'adts_header.cc',
'adts_header.h', 'adts_header.h',
'es_parser.h',
'es_parser_adts.cc', 'es_parser_adts.cc',
'es_parser_adts.h', 'es_parser_adts.h',
'es_parser_h264.cc', 'es_parser_h264.cc',
'es_parser_h264.h', 'es_parser_h264.h',
'es_parser.h',
'mp2t_media_parser.cc', 'mp2t_media_parser.cc',
'mp2t_media_parser.h', 'mp2t_media_parser.h',
'pes_packet_generator.cc',
'pes_packet_generator.h',
'pes_packet.cc',
'pes_packet.h',
'ts_packet.cc', 'ts_packet.cc',
'ts_packet.h', 'ts_packet.h',
'ts_section_pat.cc', 'ts_section_pat.cc',
@ -35,6 +39,9 @@
], ],
'dependencies': [ 'dependencies': [
'../../base/media_base.gyp:media_base', '../../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', 'adts_header_unittest.cc',
'es_parser_h264_unittest.cc', 'es_parser_h264_unittest.cc',
'mp2t_media_parser_unittest.cc', 'mp2t_media_parser_unittest.cc',
'pes_packet_generator_unittest.cc',
], ],
'dependencies': [ 'dependencies': [
'../../../testing/gtest.gyp:gtest', '../../../testing/gtest.gyp:gtest',

View File

@ -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

View File

@ -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 <stdint.h>
#include <vector>
#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<uint8_t>& data() const { return data_; }
/// @return mutable data for this PES.
std::vector<uint8_t>* 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<uint8_t> data_;
DISALLOW_COPY_AND_ASSIGN(PesPacket);
};
} // namespace mp2t
} // namespace media
} // namespace edash_packager
#endif // PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_H_

View File

@ -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<const VideoStreamInfo&>(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<const AudioStreamInfo&>(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<MediaSample> 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<uint8_t> 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<PesPacket> PesPacketGenerator::GetNextPesPacket() {
DCHECK(!pes_packets_.empty());
scoped_ptr<PesPacket> pes = pes_packets_.front().Pass();
pes_packets_.pop_front();
return pes.Pass();
}
bool PesPacketGenerator::Flush() {
return true;
}
} // namespace mp2t
} // namespace media
} // namespace edash_packager

View File

@ -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 <list>
#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<MediaSample> 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<PesPacket> 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<NalUnitToByteStreamConverter> converter_;
scoped_ptr<mp4::AACAudioSpecificConfig> 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<PesPacket> current_processing_pes_;
std::list<scoped_ptr<PesPacket>> pes_packets_;
DISALLOW_COPY_AND_ASSIGN(PesPacketGenerator);
};
} // namespace mp2t
} // namespace media
} // namespace edash_packager
#endif // PACKAGER_MEDIA_FORMATS_MP2T_PES_PACKET_GENERATOR_H_

View File

@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#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<uint8_t>* output));
};
class MockAACAudioSpecificConfig : public mp4::AACAudioSpecificConfig {
public:
MOCK_METHOD1(Parse, bool(const std::vector<uint8_t>& data));
MOCK_CONST_METHOD1(ConvertToADTS, bool(std::vector<uint8_t>* buffer));
};
scoped_refptr<VideoStreamInfo> CreateVideoStreamInfo(VideoCodec codec) {
scoped_refptr<VideoStreamInfo> 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<AudioStreamInfo> CreateAudioStreamInfo(AudioCodec codec) {
scoped_refptr<AudioStreamInfo> 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<MockNalUnitToByteStreamConverter>
mock_nal_unit_to_byte_stream_converter) {
generator_.converter_ = mock_nal_unit_to_byte_stream_converter.Pass();
}
void UseMockAACAudioSpecificConfig(
scoped_ptr<MockAACAudioSpecificConfig> mock) {
generator_.adts_converter_ = mock.Pass();
}
PesPacketGenerator generator_;
};
TEST_F(PesPacketGeneratorTest, InitializeVideo) {
scoped_refptr<VideoStreamInfo> stream_info(
CreateVideoStreamInfo(kH264VideoCodec));
EXPECT_TRUE(generator_.Initialize(*stream_info));
}
TEST_F(PesPacketGeneratorTest, InitializeVideoNonH264) {
scoped_refptr<VideoStreamInfo> stream_info(
CreateVideoStreamInfo(VideoCodec::kCodecVP9));
EXPECT_FALSE(generator_.Initialize(*stream_info));
}
TEST_F(PesPacketGeneratorTest, InitializeAudio) {
scoped_refptr<AudioStreamInfo> stream_info(
CreateAudioStreamInfo(kAacAudioCodec));
EXPECT_TRUE(generator_.Initialize(*stream_info));
}
TEST_F(PesPacketGeneratorTest, InitializeAudioNonAac) {
scoped_refptr<AudioStreamInfo> stream_info(
CreateAudioStreamInfo(AudioCodec::kCodecOpus));
EXPECT_FALSE(generator_.Initialize(*stream_info));
}
// Text is not supported yet.
TEST_F(PesPacketGeneratorTest, InitializeTextInfo) {
scoped_refptr<TextStreamInfo> 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<VideoStreamInfo> stream_info(
CreateVideoStreamInfo(kH264VideoCodec));
EXPECT_TRUE(generator_.Initialize(*stream_info));
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
scoped_refptr<MediaSample> 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<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
scoped_ptr<MockNalUnitToByteStreamConverter> 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<PesPacket> 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<VideoStreamInfo> stream_info(
CreateVideoStreamInfo(kH264VideoCodec));
EXPECT_TRUE(generator_.Initialize(*stream_info));
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
scoped_refptr<MediaSample> sample =
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
scoped_ptr<MockNalUnitToByteStreamConverter> 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<AudioStreamInfo> stream_info(
CreateAudioStreamInfo(kAacAudioCodec));
EXPECT_TRUE(generator_.Initialize(*stream_info));
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
scoped_refptr<MediaSample> sample =
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
scoped_ptr<MockAACAudioSpecificConfig> 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<PesPacket> 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<AudioStreamInfo> stream_info(
CreateAudioStreamInfo(kAacAudioCodec));
EXPECT_TRUE(generator_.Initialize(*stream_info));
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
scoped_refptr<MediaSample> sample =
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
scoped_ptr<MockAACAudioSpecificConfig> 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<VideoStreamInfo> 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<MediaSample> 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<MockNalUnitToByteStreamConverter> 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<PesPacket> 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

View File

@ -127,6 +127,31 @@ bool AACAudioSpecificConfig::Parse(const std::vector<uint8_t>& data) {
channel_config_ <= 7; channel_config_ <= 7;
} }
bool AACAudioSpecificConfig::ConvertToADTS(std::vector<uint8_t>* 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<uint8_t>& 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( uint32_t AACAudioSpecificConfig::GetOutputSamplesPerSecond(
bool sbr_in_mimetype) const { bool sbr_in_mimetype) const {
if (extension_frequency_ > 0) if (extension_frequency_ > 0)
@ -156,31 +181,6 @@ uint8_t AACAudioSpecificConfig::GetNumChannels(bool sbr_in_mimetype) const {
return num_channels_; return num_channels_;
} }
bool AACAudioSpecificConfig::ConvertToADTS(std::vector<uint8_t>* 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<uint8_t>& 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 // Currently this function only support GASpecificConfig defined in
// ISO 14496 Part 3 Table 4.1 - Syntax of GASpecificConfig() // ISO 14496 Part 3 Table 4.1 - Syntax of GASpecificConfig()
bool AACAudioSpecificConfig::SkipDecoderGASpecificConfig(BitReader* bit_reader) const { bool AACAudioSpecificConfig::SkipDecoderGASpecificConfig(BitReader* bit_reader) const {

View File

@ -17,6 +17,7 @@ class BitReader;
namespace mp4 { namespace mp4 {
// Methods are virtual for mocking.
/// This class parses the AAC information from decoder specific information /// This class parses the AAC information from decoder specific information
/// embedded in the @b esds box in an ISO BMFF file. /// embedded in the @b esds box in an ISO BMFF file.
/// Please refer to ISO 14496 Part 3 Table 1.13 - Syntax of AudioSpecificConfig /// Please refer to ISO 14496 Part 3 Table 1.13 - Syntax of AudioSpecificConfig
@ -24,7 +25,7 @@ namespace mp4 {
class AACAudioSpecificConfig { class AACAudioSpecificConfig {
public: public:
AACAudioSpecificConfig(); AACAudioSpecificConfig();
~AACAudioSpecificConfig(); virtual ~AACAudioSpecificConfig();
/// Parse the AAC config from decoder specific information embedded in an @b /// Parse the AAC config from decoder specific information embedded in an @b
/// esds box. The function will parse the data and get the /// esds box. The function will parse the data and get the
@ -32,7 +33,14 @@ class AACAudioSpecificConfig {
/// ElementaryStreamDescriptor to get audio stream configurations. /// ElementaryStreamDescriptor to get audio stream configurations.
/// @param data contains decoder specific information from an @b esds box. /// @param data contains decoder specific information from an @b esds box.
/// @return true if successful, false otherwise. /// @return true if successful, false otherwise.
bool Parse(const std::vector<uint8_t>& data); virtual bool Parse(const std::vector<uint8_t>& 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<uint8_t>* buffer) const;
/// @param sbr_in_mimetype indicates whether SBR mode is specified in the /// @param sbr_in_mimetype indicates whether SBR mode is specified in the
/// mimetype, i.e. codecs parameter contains mp4a.40.5. /// mimetype, i.e. codecs parameter contains mp4a.40.5.
@ -44,13 +52,6 @@ class AACAudioSpecificConfig {
/// @return Number of channels for the AAC stream. /// @return Number of channels for the AAC stream.
uint8_t GetNumChannels(bool sbr_in_mimetype) const; 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<uint8_t>* buffer) const;
/// @return The audio object type for this AAC config. /// @return The audio object type for this AAC config.
uint8_t audio_object_type() const { return audio_object_type_; } uint8_t audio_object_type() const { return audio_object_type_; }