Add PES packet related classes
- Define PesPacket class. - PesPacketGenerator creates PesPackets from samples. Change-Id: Icfd3656b498e0075f83ff3c789f95658f98c6144
This commit is contained in:
parent
40e1cc87d1
commit
cfd782ff40
|
@ -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,7 +32,7 @@ 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,
|
||||
virtual bool Initialize(const uint8_t* decoder_configuration_data,
|
||||
size_t decoder_configuration_data_size,
|
||||
bool escape_data);
|
||||
|
||||
|
@ -42,7 +43,8 @@ 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,
|
||||
virtual bool ConvertUnitToByteStream(const uint8_t* sample,
|
||||
size_t sample_size,
|
||||
bool is_key_frame,
|
||||
std::vector<uint8_t>* output);
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -127,6 +127,31 @@ bool AACAudioSpecificConfig::Parse(const std::vector<uint8_t>& data) {
|
|||
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(
|
||||
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<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
|
||||
// ISO 14496 Part 3 Table 4.1 - Syntax of GASpecificConfig()
|
||||
bool AACAudioSpecificConfig::SkipDecoderGASpecificConfig(BitReader* bit_reader) const {
|
||||
|
|
|
@ -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<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
|
||||
/// 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<uint8_t>* buffer) const;
|
||||
|
||||
/// @return The audio object type for this AAC config.
|
||||
uint8_t audio_object_type() const { return audio_object_type_; }
|
||||
|
||||
|
|
Loading…
Reference in New Issue