VCM: Adding API for the size(duration) of the jitter buffer.
Refers to the duration in time of the frames which are ready to be sent to the decoder. Review URL: https://webrtc-codereview.appspot.com/1319004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@3903 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
8392cd9edd
commit
381da4be9c
@ -412,6 +412,10 @@ public:
|
||||
// < 0, on error.
|
||||
virtual int32_t Decode(uint16_t maxWaitTimeMs = 200) = 0;
|
||||
|
||||
// Registers a callback which conveys the size of the render buffer.
|
||||
virtual int RegisterRenderBufferSizeCallback(
|
||||
VCMRenderBufferSizeCallback* callback) = 0;
|
||||
|
||||
// Waits for the next frame in the dual jitter buffer to become complete
|
||||
// (waits no longer than maxWaitTimeMs), then passes it to the dual decoder
|
||||
// for decoding. This will never trigger a render callback. Should be
|
||||
|
@ -182,6 +182,17 @@ class VCMQMSettingsCallback {
|
||||
}
|
||||
};
|
||||
|
||||
// Callback class used for telling the user about the size (in time) of the
|
||||
// render buffer, that is the size in time of the complete continuous frames.
|
||||
class VCMRenderBufferSizeCallback {
|
||||
public:
|
||||
virtual void RenderBufferSizeMs(int buffer_size_ms) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~VCMRenderBufferSizeCallback() {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_INTERFACE_VIDEO_CODING_DEFINES_H_
|
||||
|
@ -72,6 +72,16 @@ void VCMDecodingState::SetState(const VCMFrameBuffer* frame) {
|
||||
in_initial_state_ = false;
|
||||
}
|
||||
|
||||
void VCMDecodingState::CopyFrom(const VCMDecodingState& state) {
|
||||
sequence_num_ = state.sequence_num_;
|
||||
time_stamp_ = state.time_stamp_;
|
||||
picture_id_ = state.picture_id_;
|
||||
temporal_id_ = state.temporal_id_;
|
||||
tl0_pic_id_ = state.tl0_pic_id_;
|
||||
full_sync_ = state.full_sync_;
|
||||
in_initial_state_ = state.in_initial_state_;
|
||||
}
|
||||
|
||||
void VCMDecodingState::SetStateOneBack(const VCMFrameBuffer* frame) {
|
||||
assert(frame != NULL && frame->GetHighSeqNum() >= 0);
|
||||
sequence_num_ = static_cast<uint16_t>(frame->GetHighSeqNum()) - 1u;
|
||||
|
@ -31,6 +31,7 @@ class VCMDecodingState {
|
||||
// possible, i.e. temporal info, picture ID or sequence number.
|
||||
bool ContinuousFrame(const VCMFrameBuffer* frame) const;
|
||||
void SetState(const VCMFrameBuffer* frame);
|
||||
void CopyFrom(const VCMDecodingState& state);
|
||||
// Set the decoding state one frame back.
|
||||
void SetStateOneBack(const VCMFrameBuffer* frame);
|
||||
void UpdateEmptyFrame(const VCMFrameBuffer* frame);
|
||||
|
@ -982,6 +982,46 @@ int64_t VCMJitterBuffer::LastDecodedTimestamp() const {
|
||||
return last_decoded_state_.time_stamp();
|
||||
}
|
||||
|
||||
int VCMJitterBuffer::RenderBufferSizeMs() {
|
||||
CriticalSectionScoped cs(crit_sect_);
|
||||
CleanUpOldOrEmptyFrames();
|
||||
if (frame_list_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
FrameList::iterator frame_it = frame_list_.begin();
|
||||
VCMFrameBuffer* current_frame = *frame_it;
|
||||
// Search for a complete and continuous sequence (starting from the last
|
||||
// decoded state or current frame if in initial state).
|
||||
VCMDecodingState previous_state;
|
||||
if (last_decoded_state_.in_initial_state()) {
|
||||
// Start with a key frame.
|
||||
frame_it = find_if(frame_list_.begin(), frame_list_.end(),
|
||||
CompleteKeyFrameCriteria());
|
||||
if (frame_it == frame_list_.end()) {
|
||||
return 0;
|
||||
}
|
||||
current_frame = *frame_it;
|
||||
previous_state.SetState(current_frame);
|
||||
} else {
|
||||
previous_state.CopyFrom(last_decoded_state_);
|
||||
}
|
||||
bool continuous_complete = true;
|
||||
int64_t start_render = current_frame->RenderTimeMs();
|
||||
++frame_it;
|
||||
while (frame_it != frame_list_.end() && continuous_complete) {
|
||||
current_frame = *frame_it;
|
||||
continuous_complete = current_frame->IsSessionComplete() &&
|
||||
previous_state.ContinuousFrame(current_frame);
|
||||
previous_state.SetState(current_frame);
|
||||
++frame_it;
|
||||
}
|
||||
// Desired frame is the previous one.
|
||||
--frame_it;
|
||||
current_frame = *frame_it;
|
||||
// Got the frame, now compute the time delta.
|
||||
return static_cast<int>(current_frame->RenderTimeMs() - start_render);
|
||||
}
|
||||
|
||||
// Set the frame state to free and remove it from the sorted
|
||||
// frame list. Must be called from inside the critical section crit_sect_.
|
||||
void VCMJitterBuffer::ReleaseFrameIfNotDecoding(VCMFrameBuffer* frame) {
|
||||
|
@ -166,6 +166,9 @@ class VCMJitterBuffer {
|
||||
int64_t LastDecodedTimestamp() const;
|
||||
bool decode_with_errors() const {return decode_with_errors_;}
|
||||
|
||||
// Returns size in time (milliseconds) of complete continuous frames.
|
||||
int RenderBufferSizeMs();
|
||||
|
||||
private:
|
||||
class SequenceNumberLessThan {
|
||||
public:
|
||||
@ -211,6 +214,8 @@ class VCMJitterBuffer {
|
||||
// Can return a decodable, incomplete frame when enabled.
|
||||
FrameList::iterator FindOldestCompleteContinuousFrame();
|
||||
|
||||
// Cleans the frame list in the JB from old/empty frames.
|
||||
// Should only be called prior to actual use.
|
||||
void CleanUpOldOrEmptyFrames();
|
||||
|
||||
// Sets the "decodable" and "frame loss" flags of a frame depending on which
|
||||
|
@ -16,146 +16,12 @@
|
||||
#include "webrtc/modules/video_coding/main/source/jitter_buffer.h"
|
||||
#include "webrtc/modules/video_coding/main/source/media_opt_util.h"
|
||||
#include "webrtc/modules/video_coding/main/source/packet.h"
|
||||
#include "webrtc/modules/video_coding/main/source/stream_generator.h"
|
||||
#include "webrtc/modules/video_coding/main/test/test_util.h"
|
||||
#include "webrtc/system_wrappers/interface/clock.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
enum { kDefaultFrameRate = 25u };
|
||||
enum { kDefaultFramePeriodMs = 1000u / kDefaultFrameRate };
|
||||
const unsigned int kDefaultBitrateKbps = 1000;
|
||||
const unsigned int kFrameSize = (kDefaultBitrateKbps + kDefaultFrameRate * 4) /
|
||||
(kDefaultFrameRate * 8);
|
||||
const unsigned int kMaxPacketSize = 1500;
|
||||
|
||||
class StreamGenerator {
|
||||
public:
|
||||
StreamGenerator(uint16_t start_seq_num, uint32_t start_timestamp,
|
||||
int64_t current_time)
|
||||
: packets_(),
|
||||
sequence_number_(start_seq_num),
|
||||
timestamp_(start_timestamp),
|
||||
start_time_(current_time) {}
|
||||
|
||||
void Init(uint16_t start_seq_num, uint32_t start_timestamp,
|
||||
int64_t current_time) {
|
||||
packets_.clear();
|
||||
sequence_number_ = start_seq_num;
|
||||
timestamp_ = start_timestamp;
|
||||
start_time_ = current_time;
|
||||
memset(&packet_buffer, 0, sizeof(packet_buffer));
|
||||
}
|
||||
|
||||
void GenerateFrame(FrameType type, int num_media_packets,
|
||||
int num_empty_packets, int64_t current_time) {
|
||||
timestamp_ += 90 * (current_time - start_time_);
|
||||
// Move the sequence number counter if all packets from the previous frame
|
||||
// wasn't collected.
|
||||
sequence_number_ += packets_.size();
|
||||
packets_.clear();
|
||||
for (int i = 0; i < num_media_packets; ++i) {
|
||||
const int packet_size = (kFrameSize + num_media_packets / 2) /
|
||||
num_media_packets;
|
||||
packets_.push_back(GeneratePacket(sequence_number_,
|
||||
timestamp_,
|
||||
packet_size,
|
||||
(i == 0),
|
||||
(i == num_media_packets - 1),
|
||||
type));
|
||||
++sequence_number_;
|
||||
}
|
||||
for (int i = 0; i < num_empty_packets; ++i) {
|
||||
packets_.push_back(GeneratePacket(sequence_number_,
|
||||
timestamp_,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
kFrameEmpty));
|
||||
++sequence_number_;
|
||||
}
|
||||
}
|
||||
|
||||
VCMPacket GeneratePacket(uint16_t sequence_number,
|
||||
uint32_t timestamp,
|
||||
unsigned int size,
|
||||
bool first_packet,
|
||||
bool marker_bit,
|
||||
FrameType type) {
|
||||
EXPECT_LT(size, kMaxPacketSize);
|
||||
VCMPacket packet;
|
||||
packet.seqNum = sequence_number;
|
||||
packet.timestamp = timestamp;
|
||||
packet.frameType = type;
|
||||
packet.isFirstPacket = first_packet;
|
||||
packet.markerBit = marker_bit;
|
||||
packet.sizeBytes = size;
|
||||
packet.dataPtr = packet_buffer;
|
||||
if (packet.isFirstPacket)
|
||||
packet.completeNALU = kNaluStart;
|
||||
else if (packet.markerBit)
|
||||
packet.completeNALU = kNaluEnd;
|
||||
else
|
||||
packet.completeNALU = kNaluIncomplete;
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool PopPacket(VCMPacket* packet, int index) {
|
||||
std::list<VCMPacket>::iterator it = GetPacketIterator(index);
|
||||
if (it == packets_.end())
|
||||
return false;
|
||||
if (packet)
|
||||
*packet = (*it);
|
||||
packets_.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetPacket(VCMPacket* packet, int index) {
|
||||
std::list<VCMPacket>::iterator it = GetPacketIterator(index);
|
||||
if (it == packets_.end())
|
||||
return false;
|
||||
if (packet)
|
||||
*packet = (*it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NextPacket(VCMPacket* packet) {
|
||||
if (packets_.empty())
|
||||
return false;
|
||||
if (packet != NULL)
|
||||
*packet = packets_.front();
|
||||
packets_.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t NextSequenceNumber() const {
|
||||
if (packets_.empty())
|
||||
return sequence_number_;
|
||||
return packets_.front().seqNum;
|
||||
}
|
||||
|
||||
int PacketsRemaining() const {
|
||||
return packets_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<VCMPacket>::iterator GetPacketIterator(int index) {
|
||||
std::list<VCMPacket>::iterator it = packets_.begin();
|
||||
for (int i = 0; i < index; ++i) {
|
||||
++it;
|
||||
if (it == packets_.end()) break;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
std::list<VCMPacket> packets_;
|
||||
uint16_t sequence_number_;
|
||||
uint32_t timestamp_;
|
||||
int64_t start_time_;
|
||||
uint8_t packet_buffer[kMaxPacketSize];
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StreamGenerator);
|
||||
};
|
||||
|
||||
class TestRunningJitterBuffer : public ::testing::Test {
|
||||
protected:
|
||||
enum { kDataBufferSize = 10 };
|
||||
@ -166,7 +32,7 @@ class TestRunningJitterBuffer : public ::testing::Test {
|
||||
oldest_packet_to_nack_ = 250;
|
||||
jitter_buffer_ = new VCMJitterBuffer(clock_.get(), &event_factory_, -1, -1,
|
||||
true);
|
||||
stream_generator = new StreamGenerator(0, 0, clock_->TimeInMilliseconds());
|
||||
stream_generator_ = new StreamGenerator(0, 0, clock_->TimeInMilliseconds());
|
||||
jitter_buffer_->Start();
|
||||
jitter_buffer_->SetNackSettings(max_nack_list_size_,
|
||||
oldest_packet_to_nack_);
|
||||
@ -175,7 +41,7 @@ class TestRunningJitterBuffer : public ::testing::Test {
|
||||
|
||||
virtual void TearDown() {
|
||||
jitter_buffer_->Stop();
|
||||
delete stream_generator;
|
||||
delete stream_generator_;
|
||||
delete jitter_buffer_;
|
||||
}
|
||||
|
||||
@ -184,7 +50,7 @@ class TestRunningJitterBuffer : public ::testing::Test {
|
||||
VCMEncodedFrame* frame;
|
||||
|
||||
packet.dataPtr = data_buffer_;
|
||||
bool packet_available = stream_generator->PopPacket(&packet, index);
|
||||
bool packet_available = stream_generator_->PopPacket(&packet, index);
|
||||
EXPECT_TRUE(packet_available);
|
||||
if (!packet_available)
|
||||
return kStateError; // Return here to avoid crashes below.
|
||||
@ -197,7 +63,7 @@ class TestRunningJitterBuffer : public ::testing::Test {
|
||||
VCMEncodedFrame* frame;
|
||||
|
||||
packet.dataPtr = data_buffer_;
|
||||
bool packet_available = stream_generator->GetPacket(&packet, index);
|
||||
bool packet_available = stream_generator_->GetPacket(&packet, index);
|
||||
EXPECT_TRUE(packet_available);
|
||||
if (!packet_available)
|
||||
return kStateError; // Return here to avoid crashes below.
|
||||
@ -206,7 +72,7 @@ class TestRunningJitterBuffer : public ::testing::Test {
|
||||
}
|
||||
|
||||
VCMFrameBufferEnum InsertFrame(FrameType frame_type) {
|
||||
stream_generator->GenerateFrame(frame_type,
|
||||
stream_generator_->GenerateFrame(frame_type,
|
||||
(frame_type != kFrameEmpty) ? 1 : 0,
|
||||
(frame_type == kFrameEmpty) ? 1 : 0,
|
||||
clock_->TimeInMilliseconds());
|
||||
@ -229,7 +95,7 @@ class TestRunningJitterBuffer : public ::testing::Test {
|
||||
}
|
||||
|
||||
void DropFrame(int num_packets) {
|
||||
stream_generator->GenerateFrame(kVideoFrameDelta, num_packets, 0,
|
||||
stream_generator_->GenerateFrame(kVideoFrameDelta, num_packets, 0,
|
||||
clock_->TimeInMilliseconds());
|
||||
clock_->AdvanceTimeMilliseconds(kDefaultFramePeriodMs);
|
||||
}
|
||||
@ -250,7 +116,7 @@ class TestRunningJitterBuffer : public ::testing::Test {
|
||||
}
|
||||
|
||||
VCMJitterBuffer* jitter_buffer_;
|
||||
StreamGenerator* stream_generator;
|
||||
StreamGenerator* stream_generator_;
|
||||
scoped_ptr<SimulatedClock> clock_;
|
||||
NullEventFactory event_factory_;
|
||||
size_t max_nack_list_size_;
|
||||
@ -288,7 +154,7 @@ TEST_F(TestRunningJitterBuffer, Full) {
|
||||
|
||||
TEST_F(TestRunningJitterBuffer, EmptyPackets) {
|
||||
// Make sure a frame can get complete even though empty packets are missing.
|
||||
stream_generator->GenerateFrame(kVideoFrameKey, 3, 3,
|
||||
stream_generator_->GenerateFrame(kVideoFrameKey, 3, 3,
|
||||
clock_->TimeInMilliseconds());
|
||||
bool request_key_frame = false;
|
||||
EXPECT_EQ(kFirstPacket, InsertPacketAndPop(4));
|
||||
@ -488,11 +354,11 @@ TEST_F(TestJitterBufferNack, NoNackListReturnedBeforeFirstDecode) {
|
||||
}
|
||||
|
||||
TEST_F(TestJitterBufferNack, NackListBuiltBeforeFirstDecode) {
|
||||
stream_generator->Init(0, 0, clock_->TimeInMilliseconds());
|
||||
stream_generator_->Init(0, 0, clock_->TimeInMilliseconds());
|
||||
InsertFrame(kVideoFrameKey);
|
||||
stream_generator->GenerateFrame(kVideoFrameDelta, 2, 0,
|
||||
stream_generator_->GenerateFrame(kVideoFrameDelta, 2, 0,
|
||||
clock_->TimeInMilliseconds());
|
||||
stream_generator->NextPacket(NULL); // Drop packet.
|
||||
stream_generator_->NextPacket(NULL); // Drop packet.
|
||||
EXPECT_EQ(kFirstPacket, InsertPacketAndPop(0));
|
||||
EXPECT_TRUE(DecodeCompleteFrame());
|
||||
uint16_t nack_list_size = 0;
|
||||
@ -503,8 +369,8 @@ TEST_F(TestJitterBufferNack, NackListBuiltBeforeFirstDecode) {
|
||||
}
|
||||
|
||||
TEST_F(TestJitterBufferNack, UseNackToRecoverFirstKeyFrame) {
|
||||
stream_generator->Init(0, 0, clock_->TimeInMilliseconds());
|
||||
stream_generator->GenerateFrame(kVideoFrameKey, 3, 0,
|
||||
stream_generator_->Init(0, 0, clock_->TimeInMilliseconds());
|
||||
stream_generator_->GenerateFrame(kVideoFrameKey, 3, 0,
|
||||
clock_->TimeInMilliseconds());
|
||||
EXPECT_EQ(kFirstPacket, InsertPacketAndPop(0));
|
||||
// Drop second packet.
|
||||
@ -516,7 +382,7 @@ TEST_F(TestJitterBufferNack, UseNackToRecoverFirstKeyFrame) {
|
||||
EXPECT_EQ(1, nack_list_size);
|
||||
ASSERT_TRUE(list != NULL);
|
||||
VCMPacket packet;
|
||||
stream_generator->GetPacket(&packet, 0);
|
||||
stream_generator_->GetPacket(&packet, 0);
|
||||
EXPECT_EQ(packet.seqNum, list[0]);
|
||||
}
|
||||
|
||||
@ -530,21 +396,21 @@ TEST_F(TestJitterBufferNack, NormalOperation) {
|
||||
// ----------------------------------------------------------------
|
||||
// | 1 | 2 | .. | 8 | 9 | x | 11 | 12 | .. | 19 | x | 21 | .. | 100 |
|
||||
// ----------------------------------------------------------------
|
||||
stream_generator->GenerateFrame(kVideoFrameKey, 100, 0,
|
||||
stream_generator_->GenerateFrame(kVideoFrameKey, 100, 0,
|
||||
clock_->TimeInMilliseconds());
|
||||
clock_->AdvanceTimeMilliseconds(kDefaultFramePeriodMs);
|
||||
EXPECT_EQ(kFirstPacket, InsertPacketAndPop(0));
|
||||
// Verify that the frame is incomplete.
|
||||
EXPECT_FALSE(DecodeCompleteFrame());
|
||||
while (stream_generator->PacketsRemaining() > 1) {
|
||||
if (stream_generator->NextSequenceNumber() % 10 != 0) {
|
||||
while (stream_generator_->PacketsRemaining() > 1) {
|
||||
if (stream_generator_->NextSequenceNumber() % 10 != 0) {
|
||||
EXPECT_EQ(kIncomplete, InsertPacketAndPop(0));
|
||||
} else {
|
||||
stream_generator->NextPacket(NULL); // Drop packet
|
||||
stream_generator_->NextPacket(NULL); // Drop packet
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(kIncomplete, InsertPacketAndPop(0));
|
||||
EXPECT_EQ(0, stream_generator->PacketsRemaining());
|
||||
EXPECT_EQ(0, stream_generator_->PacketsRemaining());
|
||||
EXPECT_FALSE(DecodeCompleteFrame());
|
||||
EXPECT_FALSE(DecodeIncompleteFrame());
|
||||
uint16_t nack_list_size = 0;
|
||||
@ -563,24 +429,24 @@ TEST_F(TestJitterBufferNack, NormalOperationWrap) {
|
||||
// ------- ------------------------------------------------------------
|
||||
// | 65532 | | 65533 | 65534 | 65535 | x | 1 | .. | 9 | x | 11 |.....| 96 |
|
||||
// ------- ------------------------------------------------------------
|
||||
stream_generator->Init(65532, 0, clock_->TimeInMilliseconds());
|
||||
stream_generator_->Init(65532, 0, clock_->TimeInMilliseconds());
|
||||
InsertFrame(kVideoFrameKey);
|
||||
EXPECT_FALSE(request_key_frame);
|
||||
EXPECT_TRUE(DecodeCompleteFrame());
|
||||
stream_generator->GenerateFrame(kVideoFrameDelta, 100, 0,
|
||||
stream_generator_->GenerateFrame(kVideoFrameDelta, 100, 0,
|
||||
clock_->TimeInMilliseconds());
|
||||
EXPECT_EQ(kFirstPacket, InsertPacketAndPop(0));
|
||||
while (stream_generator->PacketsRemaining() > 1) {
|
||||
if (stream_generator->NextSequenceNumber() % 10 != 0) {
|
||||
while (stream_generator_->PacketsRemaining() > 1) {
|
||||
if (stream_generator_->NextSequenceNumber() % 10 != 0) {
|
||||
EXPECT_EQ(kIncomplete, InsertPacketAndPop(0));
|
||||
EXPECT_FALSE(request_key_frame);
|
||||
} else {
|
||||
stream_generator->NextPacket(NULL); // Drop packet.
|
||||
stream_generator_->NextPacket(NULL); // Drop packet
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(kIncomplete, InsertPacketAndPop(0));
|
||||
EXPECT_FALSE(request_key_frame);
|
||||
EXPECT_EQ(0, stream_generator->PacketsRemaining());
|
||||
EXPECT_EQ(0, stream_generator_->PacketsRemaining());
|
||||
EXPECT_FALSE(DecodeCompleteFrame());
|
||||
EXPECT_FALSE(DecodeCompleteFrame());
|
||||
uint16_t nack_list_size = 0;
|
||||
|
@ -432,6 +432,10 @@ int VCMReceiver::SetMinReceiverDelay(int desired_delay_ms) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int VCMReceiver::RenderBufferSizeMs() {
|
||||
return jitter_buffer_.RenderBufferSizeMs();
|
||||
}
|
||||
|
||||
void VCMReceiver::UpdateState(VCMReceiverState new_state) {
|
||||
CriticalSectionScoped cs(crit_sect_);
|
||||
assert(!(state_ == kPassive && new_state == kWaitForPrimaryDecode));
|
||||
|
@ -80,6 +80,10 @@ class VCMReceiver {
|
||||
void SetDecodeWithErrors(bool enable);
|
||||
bool DecodeWithErrors() const;
|
||||
|
||||
// Returns size in time (milliseconds) of complete continuous frames in the
|
||||
// jitter buffer.
|
||||
int RenderBufferSizeMs();
|
||||
|
||||
private:
|
||||
VCMEncodedFrame* FrameForDecoding(uint16_t max_wait_time_ms,
|
||||
int64_t nextrender_time_ms,
|
||||
|
125
webrtc/modules/video_coding/main/source/receiver_unittest.cc
Normal file
125
webrtc/modules/video_coding/main/source/receiver_unittest.cc
Normal file
@ -0,0 +1,125 @@
|
||||
/* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "webrtc/modules/video_coding/main/source/receiver.h"
|
||||
#include "webrtc/modules/video_coding/main/source/packet.h"
|
||||
#include "webrtc/modules/video_coding/main/source/timing.h"
|
||||
#include "webrtc/modules/video_coding/main/test/test_util.h"
|
||||
#include "webrtc/modules/video_coding/main/source/stream_generator.h"
|
||||
#include "webrtc/system_wrappers/interface/clock.h"
|
||||
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class TestVCMReceiver : public ::testing::Test {
|
||||
protected:
|
||||
enum { kDataBufferSize = 10 };
|
||||
enum { kWidth = 640 };
|
||||
enum { kHeight = 480 };
|
||||
|
||||
TestVCMReceiver()
|
||||
: clock_(new SimulatedClock(0)),
|
||||
timing_(clock_.get()),
|
||||
receiver_(&timing_, clock_.get(), &event_factory_, 1, 1, true) {
|
||||
stream_generator_.reset(new
|
||||
StreamGenerator(0, 0, clock_->TimeInMilliseconds()));
|
||||
memset(data_buffer_, 0, kDataBufferSize);
|
||||
}
|
||||
|
||||
virtual void SetUp() {
|
||||
receiver_.Reset();
|
||||
}
|
||||
|
||||
int32_t InsertPacket(int index) {
|
||||
VCMPacket packet;
|
||||
packet.dataPtr = data_buffer_;
|
||||
bool packet_available = stream_generator_->GetPacket(&packet, index);
|
||||
EXPECT_TRUE(packet_available);
|
||||
if (!packet_available)
|
||||
return kStateError; // Return here to avoid crashes below.
|
||||
// Arbitrary width and height.
|
||||
return receiver_.InsertPacket(packet, 640, 480);
|
||||
}
|
||||
|
||||
int32_t InsertPacketAndPop(int index) {
|
||||
VCMPacket packet;
|
||||
packet.dataPtr = data_buffer_;
|
||||
bool packet_available = stream_generator_->PopPacket(&packet, index);
|
||||
EXPECT_TRUE(packet_available);
|
||||
if (!packet_available)
|
||||
return kStateError; // Return here to avoid crashes below.
|
||||
return receiver_.InsertPacket(packet, kWidth, kHeight);
|
||||
}
|
||||
|
||||
int32_t InsertFrame(FrameType frame_type, bool complete) {
|
||||
int num_of_packets = complete ? 1 : 2;
|
||||
stream_generator_->GenerateFrame(
|
||||
frame_type,
|
||||
(frame_type != kFrameEmpty) ? num_of_packets : 0,
|
||||
(frame_type == kFrameEmpty) ? 1 : 0,
|
||||
clock_->TimeInMilliseconds());
|
||||
int32_t ret = InsertPacketAndPop(0);
|
||||
clock_->AdvanceTimeMilliseconds(kDefaultFramePeriodMs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
scoped_ptr<SimulatedClock> clock_;
|
||||
VCMTiming timing_;
|
||||
NullEventFactory event_factory_;
|
||||
VCMReceiver receiver_;
|
||||
scoped_ptr<StreamGenerator> stream_generator_;
|
||||
uint8_t data_buffer_[kDataBufferSize];
|
||||
};
|
||||
|
||||
TEST_F(TestVCMReceiver, RenderBufferSize_AllComplete) {
|
||||
EXPECT_EQ(0, receiver_.RenderBufferSizeMs());
|
||||
EXPECT_GE(InsertFrame(kVideoFrameKey, true), kNoError);
|
||||
int num_of_frames = 10;
|
||||
for (int i = 0; i < num_of_frames; ++i) {
|
||||
EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
|
||||
}
|
||||
EXPECT_EQ(num_of_frames * kDefaultFramePeriodMs,
|
||||
receiver_.RenderBufferSizeMs());
|
||||
}
|
||||
|
||||
TEST_F(TestVCMReceiver, RenderBufferSize_NotAllComplete) {
|
||||
EXPECT_EQ(0, receiver_.RenderBufferSizeMs());
|
||||
EXPECT_GE(InsertFrame(kVideoFrameKey, true), kNoError);
|
||||
int num_of_frames = 10;
|
||||
for (int i = 0; i < num_of_frames; ++i) {
|
||||
EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
|
||||
}
|
||||
num_of_frames++;
|
||||
EXPECT_GE(InsertFrame(kVideoFrameDelta, false), kNoError);
|
||||
for (int i = 0; i < num_of_frames; ++i) {
|
||||
EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
|
||||
}
|
||||
EXPECT_EQ(num_of_frames * kDefaultFramePeriodMs,
|
||||
receiver_.RenderBufferSizeMs());
|
||||
}
|
||||
|
||||
TEST_F(TestVCMReceiver, RenderBufferSize_NoKeyFrame) {
|
||||
EXPECT_EQ(0, receiver_.RenderBufferSizeMs());
|
||||
int num_of_frames = 10;
|
||||
for (int i = 0; i < num_of_frames; ++i) {
|
||||
EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
|
||||
}
|
||||
EXPECT_GE(InsertFrame(kVideoFrameDelta, false), kNoError);
|
||||
for (int i = 0; i < num_of_frames; ++i) {
|
||||
EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
|
||||
}
|
||||
EXPECT_EQ(0, receiver_.RenderBufferSizeMs());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
145
webrtc/modules/video_coding/main/source/stream_generator.cc
Normal file
145
webrtc/modules/video_coding/main/source/stream_generator.cc
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/main/source/stream_generator.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "webrtc/modules/video_coding/main/source/packet.h"
|
||||
#include "webrtc/modules/video_coding/main/test/test_util.h"
|
||||
#include "webrtc/system_wrappers/interface/clock.h"
|
||||
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
StreamGenerator::StreamGenerator(uint16_t start_seq_num,
|
||||
uint32_t start_timestamp,
|
||||
int64_t current_time)
|
||||
: packets_(),
|
||||
sequence_number_(start_seq_num),
|
||||
timestamp_(start_timestamp),
|
||||
start_time_(current_time) {}
|
||||
|
||||
void StreamGenerator::Init(uint16_t start_seq_num, uint32_t start_timestamp,
|
||||
int64_t current_time) {
|
||||
packets_.clear();
|
||||
sequence_number_ = start_seq_num;
|
||||
timestamp_ = start_timestamp;
|
||||
start_time_ = current_time;
|
||||
memset(&packet_buffer, 0, sizeof(packet_buffer));
|
||||
}
|
||||
|
||||
void StreamGenerator::GenerateFrame(FrameType type,
|
||||
int num_media_packets,
|
||||
int num_empty_packets,
|
||||
int64_t current_time) {
|
||||
timestamp_ += 90 * (current_time - start_time_);
|
||||
// Move the sequence number counter if all packets from the previous frame
|
||||
// wasn't collected.
|
||||
sequence_number_ += packets_.size();
|
||||
packets_.clear();
|
||||
for (int i = 0; i < num_media_packets; ++i) {
|
||||
const int packet_size = (kFrameSize + num_media_packets / 2) /
|
||||
num_media_packets;
|
||||
bool marker_bit = (i == num_media_packets - 1);
|
||||
packets_.push_back(GeneratePacket(sequence_number_,
|
||||
timestamp_,
|
||||
packet_size,
|
||||
(i == 0),
|
||||
marker_bit,
|
||||
type));
|
||||
++sequence_number_;
|
||||
}
|
||||
for (int i = 0; i < num_empty_packets; ++i) {
|
||||
packets_.push_back(GeneratePacket(sequence_number_,
|
||||
timestamp_,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
kFrameEmpty));
|
||||
++sequence_number_;
|
||||
}
|
||||
}
|
||||
|
||||
VCMPacket StreamGenerator::GeneratePacket(uint16_t sequence_number,
|
||||
uint32_t timestamp,
|
||||
unsigned int size,
|
||||
bool first_packet,
|
||||
bool marker_bit,
|
||||
FrameType type) {
|
||||
EXPECT_LT(size, kMaxPacketSize);
|
||||
VCMPacket packet;
|
||||
packet.seqNum = sequence_number;
|
||||
packet.timestamp = timestamp;
|
||||
packet.frameType = type;
|
||||
packet.isFirstPacket = first_packet;
|
||||
packet.markerBit = marker_bit;
|
||||
packet.sizeBytes = size;
|
||||
packet.dataPtr = packet_buffer;
|
||||
if (packet.isFirstPacket)
|
||||
packet.completeNALU = kNaluStart;
|
||||
else if (packet.markerBit)
|
||||
packet.completeNALU = kNaluEnd;
|
||||
else
|
||||
packet.completeNALU = kNaluIncomplete;
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool StreamGenerator::PopPacket(VCMPacket* packet, int index) {
|
||||
std::list<VCMPacket>::iterator it = GetPacketIterator(index);
|
||||
if (it == packets_.end())
|
||||
return false;
|
||||
if (packet)
|
||||
*packet = (*it);
|
||||
packets_.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StreamGenerator::GetPacket(VCMPacket* packet, int index) {
|
||||
std::list<VCMPacket>::iterator it = GetPacketIterator(index);
|
||||
if (it == packets_.end())
|
||||
return false;
|
||||
if (packet)
|
||||
*packet = (*it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StreamGenerator::NextPacket(VCMPacket* packet) {
|
||||
if (packets_.empty())
|
||||
return false;
|
||||
if (packet != NULL)
|
||||
*packet = packets_.front();
|
||||
packets_.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t StreamGenerator::NextSequenceNumber() const {
|
||||
if (packets_.empty())
|
||||
return sequence_number_;
|
||||
return packets_.front().seqNum;
|
||||
}
|
||||
|
||||
int StreamGenerator::PacketsRemaining() const {
|
||||
return packets_.size();
|
||||
}
|
||||
|
||||
std::list<VCMPacket>::iterator StreamGenerator::GetPacketIterator(int index) {
|
||||
std::list<VCMPacket>::iterator it = packets_.begin();
|
||||
for (int i = 0; i < index; ++i) {
|
||||
++it;
|
||||
if (it == packets_.end()) break;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
72
webrtc/modules/video_coding/main/source/stream_generator.h
Normal file
72
webrtc/modules/video_coding/main/source/stream_generator.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_MAIN_SOURCE_STREAM_GENERATOR_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_MAIN_SOURCE_STREAM_GENERATOR_H_
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "webrtc/modules/video_coding/main/source/packet.h"
|
||||
#include "webrtc/modules/video_coding/main/test/test_util.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
const unsigned int kDefaultBitrateKbps = 1000;
|
||||
enum { kDefaultFrameRate = 25u };
|
||||
const unsigned int kMaxPacketSize = 1500;
|
||||
const unsigned int kFrameSize = (kDefaultBitrateKbps + kDefaultFrameRate * 4) /
|
||||
(kDefaultFrameRate * 8);
|
||||
enum { kDefaultFramePeriodMs = 1000u / kDefaultFrameRate };
|
||||
|
||||
|
||||
|
||||
class StreamGenerator {
|
||||
public:
|
||||
StreamGenerator(uint16_t start_seq_num, uint32_t start_timestamp,
|
||||
int64_t current_time);
|
||||
void Init(uint16_t start_seq_num, uint32_t start_timestamp,
|
||||
int64_t current_time);
|
||||
|
||||
void GenerateFrame(FrameType type, int num_media_packets,
|
||||
int num_empty_packets, int64_t current_time);
|
||||
|
||||
VCMPacket GeneratePacket(uint16_t sequence_number,
|
||||
uint32_t timestamp,
|
||||
unsigned int size,
|
||||
bool first_packet,
|
||||
bool marker_bit,
|
||||
FrameType type);
|
||||
|
||||
bool PopPacket(VCMPacket* packet, int index);
|
||||
|
||||
bool GetPacket(VCMPacket* packet, int index);
|
||||
|
||||
bool NextPacket(VCMPacket* packet);
|
||||
|
||||
uint16_t NextSequenceNumber() const;
|
||||
|
||||
int PacketsRemaining() const;
|
||||
|
||||
private:
|
||||
std::list<VCMPacket>::iterator GetPacketIterator(int index);
|
||||
|
||||
std::list<VCMPacket> packets_;
|
||||
uint16_t sequence_number_;
|
||||
uint32_t timestamp_;
|
||||
int64_t start_time_;
|
||||
uint8_t packet_buffer[kMaxPacketSize];
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StreamGenerator);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_MAIN_SOURCE_STREAM_GENERATOR_H_
|
@ -62,6 +62,7 @@ VideoCodingModuleImpl::VideoCodingModuleImpl(const int32_t id,
|
||||
_frameStorageCallback(NULL),
|
||||
_receiveStatsCallback(NULL),
|
||||
_packetRequestCallback(NULL),
|
||||
render_buffer_callback_(NULL),
|
||||
_decoder(NULL),
|
||||
_dualDecoder(NULL),
|
||||
#ifdef DEBUG_DECODER_BIT_STREAM
|
||||
@ -154,6 +155,12 @@ VideoCodingModuleImpl::Process()
|
||||
_receiver.ReceiveStatistics(&bitRate, &frameRate);
|
||||
_receiveStatsCallback->ReceiveStatistics(bitRate, frameRate);
|
||||
}
|
||||
|
||||
// Size of render buffer.
|
||||
if (render_buffer_callback_) {
|
||||
int buffer_size_ms = _receiver.RenderBufferSizeMs();
|
||||
render_buffer_callback_->RenderBufferSizeMs(buffer_size_ms);
|
||||
}
|
||||
}
|
||||
|
||||
// Send-side statistics
|
||||
@ -870,6 +877,13 @@ VideoCodingModuleImpl::RegisterPacketRequestCallback(
|
||||
return VCM_OK;
|
||||
}
|
||||
|
||||
int VideoCodingModuleImpl::RegisterRenderBufferSizeCallback(
|
||||
VCMRenderBufferSizeCallback* callback) {
|
||||
CriticalSectionScoped cs(_receiveCritSect);
|
||||
render_buffer_callback_ = callback;
|
||||
return VCM_OK;
|
||||
}
|
||||
|
||||
// Decode next frame, blocking.
|
||||
// Should be called as often as possible to get the most out of the decoder.
|
||||
int32_t
|
||||
|
@ -196,6 +196,10 @@ public:
|
||||
virtual int32_t RegisterPacketRequestCallback(
|
||||
VCMPacketRequestCallback* callback);
|
||||
|
||||
// Render buffer size callback.
|
||||
virtual int RegisterRenderBufferSizeCallback(
|
||||
VCMRenderBufferSizeCallback* callback);
|
||||
|
||||
// Decode next frame, blocks for a maximum of maxWaitTimeMs milliseconds.
|
||||
// Should be called as often as possible to get the most out of the decoder.
|
||||
virtual int32_t Decode(uint16_t maxWaitTimeMs = 200);
|
||||
@ -294,6 +298,7 @@ private:
|
||||
VCMFrameStorageCallback* _frameStorageCallback;
|
||||
VCMReceiveStatisticsCallback* _receiveStatsCallback;
|
||||
VCMPacketRequestCallback* _packetRequestCallback;
|
||||
VCMRenderBufferSizeCallback* render_buffer_callback_;
|
||||
VCMGenericDecoder* _decoder;
|
||||
VCMGenericDecoder* _dualDecoder;
|
||||
#ifdef DEBUG_DECODER_BIT_STREAM
|
||||
|
@ -105,7 +105,10 @@
|
||||
'../interface/mock/mock_vcm_callbacks.h',
|
||||
'decoding_state_unittest.cc',
|
||||
'jitter_buffer_unittest.cc',
|
||||
'receiver_unittest.cc',
|
||||
'session_info_unittest.cc',
|
||||
'stream_generator.cc',
|
||||
'stream_generator.h',
|
||||
'video_coding_robustness_unittest.cc',
|
||||
'video_coding_impl_unittest.cc',
|
||||
'qm_select_unittest.cc',
|
||||
|
Loading…
x
Reference in New Issue
Block a user