Commit ff757030 authored by xhwang@chromium.org's avatar xhwang@chromium.org

No EOS frame in {Audio|Video}Decoder::OutputCB.

This is a follow-up CL of r276344.

Currently, when audio and video decoders receive an EOS buffer, they do the following in order:
1, Flush the codec, return all internally buffered frames through the OutputCB.
2, Return a EOS frame through the OutputCB.
3, Return the DecodeCB.

Since the DecoderStream knows when an input buffer is an EOS, when the DecodeCB is returned, DecoderStream knows that decoding has finished. Therefore, step (2) is redundant.

This CL drops step (2) which simplifies a lot of code.

BUG=385872

Review URL: https://codereview.chromium.org/339653003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278232 0039d316-1c4b-4281-b951-d872f2087c98
parent fce6ab5a
......@@ -30,7 +30,8 @@ class MEDIA_EXPORT AudioDecoder {
kDecryptError // Decrypting error happened.
};
// Callback to return decoded buffers.
// Callback for AudioDecoder to return a decoded frame whenever it becomes
// available. Only non-EOS frames should be returned via this callback.
typedef base::Callback<void(const scoped_refptr<AudioBuffer>&)> OutputCB;
// Callback for Decode(). Called after the decoder has completed decoding
......
......@@ -29,7 +29,8 @@ class MEDIA_EXPORT VideoDecoder {
kDecryptError // Decrypting error happened.
};
// Callback to return decode frames.
// Callback for VideoDecoder to return a decoded frame whenever it becomes
// available. Only non-EOS frames should be returned via this callback.
typedef base::Callback<void(const scoped_refptr<VideoFrame>&)> OutputCB;
// Callback type for Decode(). Called after the decoder has completed decoding
......
......@@ -290,10 +290,25 @@ class AudioRendererImplTest : public ::testing::Test {
}
void DeliverEndOfStream() {
// Repeatedly return EOS buffer
while (!decode_cb_.is_null()) {
DeliverBuffer(AudioDecoder::kOk, AudioBuffer::CreateEOSBuffer());
}
DCHECK(!decode_cb_.is_null());
// Return EOS buffer to trigger EOS frame.
EXPECT_CALL(demuxer_stream_, Read(_))
.WillOnce(RunCallback<0>(DemuxerStream::kOk,
DecoderBuffer::CreateEOSBuffer()));
// Satify pending |decode_cb_| to trigger a new DemuxerStream::Read().
message_loop_.PostTask(
FROM_HERE,
base::Bind(base::ResetAndReturn(&decode_cb_), AudioDecoder::kOk));
WaitForPendingRead();
message_loop_.PostTask(
FROM_HERE,
base::Bind(base::ResetAndReturn(&decode_cb_), AudioDecoder::kOk));
message_loop_.RunUntilIdle();
}
// Delivers frames until |renderer_|'s internal buffer is full and no longer
......@@ -480,7 +495,7 @@ class AudioRendererImplTest : public ::testing::Test {
void DeliverBuffer(AudioDecoder::Status status,
const scoped_refptr<AudioBuffer>& buffer) {
CHECK(!decode_cb_.is_null());
if (buffer)
if (buffer && !buffer->end_of_stream())
output_cb_.Run(buffer);
base::ResetAndReturn(&decode_cb_).Run(status);
......
......@@ -89,30 +89,32 @@ template <DemuxerStream::Type StreamType>
void DecoderStream<StreamType>::Read(const ReadCB& read_cb) {
FUNCTION_DVLOG(2);
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER ||
state_ == STATE_ERROR || state_ == STATE_REINITIALIZING_DECODER ||
state_ == STATE_PENDING_DEMUXER_READ)
<< state_;
DCHECK(state_ != STATE_UNINITIALIZED && state_ != STATE_INITIALIZING &&
state_ != STATE_STOPPED) << state_;
// No two reads in the flight at any time.
DCHECK(read_cb_.is_null());
// No read during resetting or stopping process.
DCHECK(reset_cb_.is_null());
DCHECK(stop_cb_.is_null());
read_cb_ = read_cb;
if (state_ == STATE_ERROR) {
task_runner_->PostTask(FROM_HERE,
base::Bind(base::ResetAndReturn(&read_cb_),
DECODE_ERROR,
scoped_refptr<Output>()));
task_runner_->PostTask(
FROM_HERE, base::Bind(read_cb, DECODE_ERROR, scoped_refptr<Output>()));
return;
}
if (state_ == STATE_END_OF_STREAM && ready_outputs_.empty()) {
task_runner_->PostTask(
FROM_HERE, base::Bind(read_cb, OK, StreamTraits::CreateEOSOutput()));
return;
}
if (!ready_outputs_.empty()) {
task_runner_->PostTask(FROM_HERE, base::Bind(
base::ResetAndReturn(&read_cb_), OK, ready_outputs_.front()));
task_runner_->PostTask(FROM_HERE,
base::Bind(read_cb, OK, ready_outputs_.front()));
ready_outputs_.pop_front();
} else {
read_cb_ = read_cb;
}
if (state_ == STATE_NORMAL && CanDecodeMore())
......@@ -337,35 +339,43 @@ void DecoderStream<StreamType>::OnDecodeDone(int buffer_size,
ready_outputs_.clear();
if (!read_cb_.is_null())
SatisfyRead(DECODE_ERROR, NULL);
break;
return;
case Decoder::kAborted:
// Decoder can return kAborted only when Reset is pending.
NOTREACHED();
break;
return;
case Decoder::kOk:
// Any successful decode counts!
if (buffer_size > 0) {
if (buffer_size > 0)
StreamTraits::ReportStatistics(statistics_cb_, buffer_size);
}
if (state_ == STATE_NORMAL) {
if (CanDecodeMore() && !end_of_stream)
if (end_of_stream) {
state_ = STATE_END_OF_STREAM;
if (ready_outputs_.empty() && !read_cb_.is_null())
SatisfyRead(OK, StreamTraits::CreateEOSOutput());
return;
}
if (CanDecodeMore())
ReadFromDemuxerStream();
} else if (state_ == STATE_FLUSHING_DECODER) {
if (!pending_decode_requests_)
ReinitializeDecoder();
return;
}
break;
if (state_ == STATE_FLUSHING_DECODER && !pending_decode_requests_)
ReinitializeDecoder();
return;
}
}
template <DemuxerStream::Type StreamType>
void DecoderStream<StreamType>::OnDecodeOutputReady(
const scoped_refptr<Output>& output) {
FUNCTION_DVLOG(2) << output;
FUNCTION_DVLOG(2) << ": " << output->timestamp().InMilliseconds() << " ms";
DCHECK(output);
DCHECK(!output->end_of_stream());
DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER ||
state_ == STATE_PENDING_DEMUXER_READ || state_ == STATE_ERROR)
<< state_;
......@@ -382,20 +392,16 @@ void DecoderStream<StreamType>::OnDecodeOutputReady(
// TODO(xhwang): VideoDecoder doesn't need to return EOS after it's flushed.
// Fix all decoders and remove this block.
if (state_ == STATE_FLUSHING_DECODER && output->end_of_stream()) {
// ReinitializeDecoder() will be called from OnDecodeDone().
return;
}
// Store decoded output.
ready_outputs_.push_back(output);
if (read_cb_.is_null())
return;
// Satisfy outstanding read request, if any.
if (!read_cb_.is_null()) {
scoped_refptr<Output> read_result = ready_outputs_.front();
ready_outputs_.pop_front();
SatisfyRead(OK, output);
}
scoped_refptr<Output> read_result = ready_outputs_.front();
ready_outputs_.pop_front();
SatisfyRead(OK, output);
}
template <DemuxerStream::Type StreamType>
......@@ -415,7 +421,9 @@ template <DemuxerStream::Type StreamType>
void DecoderStream<StreamType>::OnBufferReady(
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
FUNCTION_DVLOG(2) << ": " << status;
FUNCTION_DVLOG(2) << ": " << status << ", "
<< buffer->AsHumanReadableString();
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ == STATE_PENDING_DEMUXER_READ || state_ == STATE_ERROR ||
state_ == STATE_STOPPED)
......@@ -537,7 +545,7 @@ void DecoderStream<StreamType>::ResetDecoder() {
FUNCTION_DVLOG(2);
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER ||
state_ == STATE_ERROR) << state_;
state_ == STATE_ERROR || state_ == STATE_END_OF_STREAM) << state_;
DCHECK(!reset_cb_.is_null());
decoder_->Reset(base::Bind(&DecoderStream<StreamType>::OnDecoderReset,
......@@ -549,7 +557,7 @@ void DecoderStream<StreamType>::OnDecoderReset() {
FUNCTION_DVLOG(2);
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER ||
state_ == STATE_ERROR) << state_;
state_ == STATE_ERROR || state_ == STATE_END_OF_STREAM) << state_;
// If Reset() was called during pending read, read callback should be fired
// before the reset callback is fired.
DCHECK(read_cb_.is_null());
......@@ -557,6 +565,7 @@ void DecoderStream<StreamType>::OnDecoderReset() {
DCHECK(stop_cb_.is_null());
if (state_ != STATE_FLUSHING_DECODER) {
state_ = STATE_NORMAL;
base::ResetAndReturn(&reset_cb_).Run();
return;
}
......
......@@ -121,6 +121,7 @@ class MEDIA_EXPORT DecoderStream {
STATE_FLUSHING_DECODER,
STATE_PENDING_DEMUXER_READ,
STATE_REINITIALIZING_DECODER,
STATE_END_OF_STREAM, // End of stream reached; returns EOS on all reads.
STATE_STOPPED,
STATE_ERROR
};
......
......@@ -5,10 +5,12 @@
#include "media/filters/decoder_stream_traits.h"
#include "base/logging.h"
#include "media/base/audio_buffer.h"
#include "media/base/audio_decoder.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/video_decoder.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
namespace media {
......@@ -52,6 +54,11 @@ DecoderStreamTraits<DemuxerStream::AUDIO>::DecoderConfigType
return stream.audio_decoder_config();
}
scoped_refptr<DecoderStreamTraits<DemuxerStream::AUDIO>::OutputType>
DecoderStreamTraits<DemuxerStream::AUDIO>::CreateEOSOutput() {
return OutputType::CreateEOSBuffer();
}
std::string DecoderStreamTraits<DemuxerStream::VIDEO>::ToString() {
return "Video";
}
......@@ -94,4 +101,9 @@ DecoderStreamTraits<DemuxerStream::VIDEO>::DecoderConfigType
return stream.video_decoder_config();
}
scoped_refptr<DecoderStreamTraits<DemuxerStream::VIDEO>::OutputType>
DecoderStreamTraits<DemuxerStream::VIDEO>::CreateEOSOutput() {
return OutputType::CreateEOSFrame();
}
} // namespace media
......@@ -42,6 +42,7 @@ struct DecoderStreamTraits<DemuxerStream::AUDIO> {
static void ReportStatistics(const StatisticsCB& statistics_cb,
int bytes_decoded);
static DecoderConfigType GetDecoderConfig(DemuxerStream& stream);
static scoped_refptr<OutputType> CreateEOSOutput();
};
template <>
......@@ -65,6 +66,7 @@ struct DecoderStreamTraits<DemuxerStream::VIDEO> {
static void ReportStatistics(const StatisticsCB& statistics_cb,
int bytes_decoded);
static DecoderConfigType GetDecoderConfig(DemuxerStream& stream);
static scoped_refptr<OutputType> CreateEOSOutput();
};
} // namespace media
......
......@@ -294,12 +294,8 @@ void DecryptingAudioDecoder::DeliverFrame(
if (status == Decryptor::kNeedMoreData) {
DVLOG(2) << "DeliverFrame() - kNeedMoreData";
if (scoped_pending_buffer_to_decode->end_of_stream()) {
state_ = kDecodeFinished;
output_cb_.Run(AudioBuffer::CreateEOSBuffer());
} else {
state_ = kIdle;
}
state_ = scoped_pending_buffer_to_decode->end_of_stream() ? kDecodeFinished
: kIdle;
base::ResetAndReturn(&decode_cb_).Run(kOk);
return;
}
......
......@@ -59,10 +59,6 @@ ACTION_P(RunCallbackIfNotNull, param) {
arg0.Run(param);
}
MATCHER(IsEndOfStream, "end of stream") {
return (arg->end_of_stream());
}
} // namespace
class DecryptingAudioDecoderTest : public testing::Test {
......@@ -180,9 +176,7 @@ class DecryptingAudioDecoderTest : public testing::Test {
// of stream state. This function must be called after
// EnterNormalDecodingState() to work.
void EnterEndOfStreamState() {
// The codec in the |decryptor_| will be flushed. We expect kDecodingDelay
// frames to be returned followed by a EOS frame.
EXPECT_CALL(*this, FrameReady(IsEndOfStream()));
// The codec in the |decryptor_| will be flushed.
EXPECT_CALL(*this, FrameReady(decoded_frame_))
.Times(kDecodingDelay);
DecodeAndExpect(DecoderBuffer::CreateEOSBuffer(), AudioDecoder::kOk);
......
......@@ -31,7 +31,7 @@ DecryptingVideoDecoder::DecryptingVideoDecoder(
weak_factory_(this) {}
void DecryptingVideoDecoder::Initialize(const VideoDecoderConfig& config,
bool /* live_mode */,
bool /* low_delay */,
const PipelineStatusCB& status_cb,
const OutputCB& output_cb) {
DVLOG(2) << "Initialize()";
......@@ -82,7 +82,6 @@ void DecryptingVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
// Return empty frames if decoding has finished.
if (state_ == kDecodeFinished) {
output_cb_.Run(VideoFrame::CreateEOSFrame());
base::ResetAndReturn(&decode_cb_).Run(kOk);
return;
}
......@@ -277,12 +276,8 @@ void DecryptingVideoDecoder::DeliverFrame(
if (status == Decryptor::kNeedMoreData) {
DVLOG(2) << "DeliverFrame() - kNeedMoreData";
if (scoped_pending_buffer_to_decode->end_of_stream()) {
state_ = kDecodeFinished;
output_cb_.Run(media::VideoFrame::CreateEOSFrame());
} else {
state_ = kIdle;
}
state_ = scoped_pending_buffer_to_decode->end_of_stream() ? kDecodeFinished
: kIdle;
base::ResetAndReturn(&decode_cb_).Run(kOk);
return;
}
......
......@@ -33,7 +33,7 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder {
// VideoDecoder implementation.
virtual void Initialize(const VideoDecoderConfig& config,
bool live_mode,
bool low_delay,
const PipelineStatusCB& status_cb,
const OutputCB& output_cb) OVERRIDE;
virtual void Decode(const scoped_refptr<DecoderBuffer>& buffer,
......
......@@ -53,10 +53,6 @@ ACTION_P2(ResetAndRunCallback, callback, param) {
base::ResetAndReturn(callback).Run(param);
}
MATCHER(IsEndOfStream, "end of stream") {
return (arg->end_of_stream());
}
} // namespace
class DecryptingVideoDecoderTest : public testing::Test {
......@@ -156,9 +152,7 @@ class DecryptingVideoDecoderTest : public testing::Test {
// of stream state. This function must be called after
// EnterNormalDecodingState() to work.
void EnterEndOfStreamState() {
// The codec in the |decryptor_| will be flushed. We expect kDecodingDelay
// frames to be returned followed by a EOS frame.
EXPECT_CALL(*this, FrameReady(IsEndOfStream()));
// The codec in the |decryptor_| will be flushed.
EXPECT_CALL(*this, FrameReady(decoded_video_frame_))
.Times(kDecodingDelay);
DecodeAndExpect(DecoderBuffer::CreateEOSBuffer(), VideoDecoder::kOk);
......
......@@ -34,24 +34,30 @@ FakeDemuxerStream::FakeDemuxerStream(int num_configs,
int num_buffers_in_one_config,
bool is_encrypted)
: task_runner_(base::MessageLoopProxy::current()),
num_configs_left_(num_configs),
num_configs_(num_configs),
num_buffers_in_one_config_(num_buffers_in_one_config),
config_changes_(num_configs > 1),
is_encrypted_(is_encrypted),
num_buffers_left_in_current_config_(num_buffers_in_one_config),
num_buffers_returned_(0),
current_timestamp_(base::TimeDelta::FromMilliseconds(kStartTimestampMs)),
duration_(base::TimeDelta::FromMilliseconds(kDurationMs)),
next_coded_size_(kStartWidth, kStartHeight),
next_read_num_(0),
read_to_hold_(-1) {
DCHECK_GT(num_configs_left_, 0);
DCHECK_GT(num_buffers_in_one_config_, 0);
DCHECK_GT(num_configs, 0);
DCHECK_GT(num_buffers_in_one_config, 0);
Initialize();
UpdateVideoDecoderConfig();
}
FakeDemuxerStream::~FakeDemuxerStream() {}
void FakeDemuxerStream::Initialize() {
DCHECK_EQ(-1, read_to_hold_);
num_configs_left_ = num_configs_;
num_buffers_left_in_current_config_ = num_buffers_in_one_config_;
num_buffers_returned_ = 0;
current_timestamp_ = base::TimeDelta::FromMilliseconds(kStartTimestampMs);
duration_ = base::TimeDelta::FromMilliseconds(kDurationMs);
next_coded_size_ = gfx::Size(kStartWidth, kStartHeight);
next_read_num_ = 0;
}
void FakeDemuxerStream::Read(const ReadCB& read_cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(read_cb_.is_null());
......@@ -127,6 +133,11 @@ void FakeDemuxerStream::Reset() {
base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
}
void FakeDemuxerStream::SeekToStart() {
Reset();
Initialize();
}
void FakeDemuxerStream::UpdateVideoDecoderConfig() {
const gfx::Rect kVisibleRect(kStartWidth, kStartHeight);
video_decoder_config_.Initialize(
......
......@@ -35,6 +35,8 @@ class FakeDemuxerStream : public DemuxerStream {
virtual void EnableBitstreamConverter() OVERRIDE;
virtual bool SupportsConfigChanges() OVERRIDE;
void Initialize();
int num_buffers_returned() const { return num_buffers_returned_; }
// Upon the next read, holds the read callback until SatisfyRead() or Reset()
......@@ -56,16 +58,22 @@ class FakeDemuxerStream : public DemuxerStream {
// always clears |hold_next_read_|.
void Reset();
// Reset() this demuxer stream and set the reading position to the start of
// the stream.
void SeekToStart();
private:
void UpdateVideoDecoderConfig();
void DoRead();
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
const int num_configs_;
const int num_buffers_in_one_config_;
const bool config_changes_;
const bool is_encrypted_;
int num_configs_left_;
int num_buffers_in_one_config_;
bool config_changes_;
bool is_encrypted_;
// Number of frames left with the current decoder config.
int num_buffers_left_in_current_config_;
......
......@@ -13,9 +13,9 @@
namespace media {
static const int kNumBuffersInOneConfig = 9;
static const int kNumBuffersToReadFirst = 5;
static const int kNumConfigs = 3;
const int kNumBuffersInOneConfig = 9;
const int kNumBuffersToReadFirst = 5;
const int kNumConfigs = 3;
COMPILE_ASSERT(kNumBuffersToReadFirst < kNumBuffersInOneConfig,
do_not_read_too_many_buffers);
COMPILE_ASSERT(kNumConfigs > 0, need_multiple_configs_to_trigger_config_change);
......@@ -24,7 +24,8 @@ class FakeDemuxerStreamTest : public testing::Test {
public:
FakeDemuxerStreamTest()
: status_(DemuxerStream::kAborted),
read_pending_(false) {}
read_pending_(false),
num_buffers_received_(0) {}
virtual ~FakeDemuxerStreamTest() {}
void BufferReady(DemuxerStream::Status status,
......@@ -33,6 +34,8 @@ class FakeDemuxerStreamTest : public testing::Test {
read_pending_ = false;
status_ = status;
buffer_ = buffer;
if (status == DemuxerStream::kOk && !buffer->end_of_stream())
num_buffers_received_++;
}
enum ReadResult {
......@@ -48,12 +51,14 @@ class FakeDemuxerStreamTest : public testing::Test {
new FakeDemuxerStream(kNumConfigs, kNumBuffersInOneConfig, false));
for (int i = 0; i < kNumBuffersToReadFirst; ++i)
ReadAndExpect(OK);
DCHECK_EQ(kNumBuffersToReadFirst, num_buffers_received_);
}
void EnterBeforeEOSState() {
stream_.reset(new FakeDemuxerStream(1, kNumBuffersInOneConfig, false));
for (int i = 0; i < kNumBuffersInOneConfig; ++i)
ReadAndExpect(OK);
DCHECK_EQ(kNumBuffersInOneConfig, num_buffers_received_);
}
void ExpectReadResult(ReadResult result) {
......@@ -128,23 +133,12 @@ class FakeDemuxerStreamTest : public testing::Test {
ExpectReadResult(ABORTED);
}
void TestRead(int num_configs,
int num_buffers_in_one_config,
bool is_encrypted) {
stream_.reset(new FakeDemuxerStream(
num_configs, num_buffers_in_one_config, is_encrypted));
int num_buffers_received = 0;
const VideoDecoderConfig& config = stream_->video_decoder_config();
EXPECT_TRUE(config.IsValidConfig());
EXPECT_EQ(is_encrypted, config.is_encrypted());
void ReadAllBuffers(int num_configs, int num_buffers_in_one_config) {
DCHECK_EQ(0, num_buffers_received_);
for (int i = 0; i < num_configs; ++i) {
for (int j = 0; j < num_buffers_in_one_config; ++j) {
ReadAndExpect(OK);
num_buffers_received++;
EXPECT_EQ(num_buffers_received, stream_->num_buffers_returned());
EXPECT_EQ(num_buffers_received_, stream_->num_buffers_returned());
}
if (i == num_configs - 1)
......@@ -156,7 +150,20 @@ class FakeDemuxerStreamTest : public testing::Test {
// Will always get EOS after we hit EOS.
ReadAndExpect(EOS);
EXPECT_EQ(num_configs * num_buffers_in_one_config, num_buffers_received);
EXPECT_EQ(num_configs * num_buffers_in_one_config, num_buffers_received_);
}
void TestRead(int num_configs,
int num_buffers_in_one_config,
bool is_encrypted) {
stream_.reset(new FakeDemuxerStream(
num_configs, num_buffers_in_one_config, is_encrypted));
const VideoDecoderConfig& config = stream_->video_decoder_config();
EXPECT_TRUE(config.IsValidConfig());
EXPECT_EQ(is_encrypted, config.is_encrypted());
ReadAllBuffers(num_configs, num_buffers_in_one_config);
}
base::MessageLoop message_loop_;
......@@ -254,4 +261,25 @@ TEST_F(FakeDemuxerStreamTest, NoConfigChanges) {
ReadAndExpect(EOS);
}
TEST_F(FakeDemuxerStreamTest, SeekToStart_Normal) {
EnterNormalReadState();
stream_->SeekToStart();
num_buffers_received_ = 0;
ReadAllBuffers(kNumConfigs, kNumBuffersInOneConfig);
}
TEST_F(FakeDemuxerStreamTest, SeekToStart_BeforeEOS) {
EnterBeforeEOSState();
stream_->SeekToStart();
num_buffers_received_ = 0;
ReadAllBuffers(1, kNumBuffersInOneConfig);
}
TEST_F(FakeDemuxerStreamTest, SeekToStart_AfterEOS) {
TestRead(3, 5, false);
stream_->SeekToStart();
num_buffers_received_ = 0;
ReadAllBuffers(3, 5);
}
} // namespace media
......@@ -225,7 +225,6 @@ void FakeVideoDecoder::RunDecodeCallback(const DecodeCB& decode_cb) {
output_cb_.Run(decoded_frames_.front());
decoded_frames_.pop_front();
}
output_cb_.Run(VideoFrame::CreateEOSFrame());
} else if (!decoded_frames_.empty()) {
output_cb_.Run(decoded_frames_.front());
decoded_frames_.pop_front();
......
......@@ -72,17 +72,16 @@ class FakeVideoDecoderTest
}
void FrameReady(const scoped_refptr<VideoFrame>& frame) {
DCHECK(!frame->end_of_stream());
last_decoded_frame_ = frame;
if (!frame->end_of_stream())
num_decoded_frames_++;
num_decoded_frames_++;
}
enum CallbackResult {
PENDING,
OK,
NOT_ENOUGH_DATA,
ABORTED,
EOS
ABORTED
};
void ExpectReadResult(CallbackResult result) {
......@@ -94,7 +93,6 @@ class FakeVideoDecoderTest
EXPECT_EQ(0, pending_decode_requests_);
ASSERT_EQ(VideoDecoder::kOk, last_decode_status_);
ASSERT_TRUE(last_decoded_frame_);
EXPECT_FALSE(last_decoded_frame_->end_of_stream());
break;
case NOT_ENOUGH_DATA:
EXPECT_EQ(0, pending_decode_requests_);
......@@ -106,12 +104,6 @@ class FakeVideoDecoderTest
ASSERT_EQ(VideoDecoder::kAborted, last_decode_status_);
EXPECT_FALSE(last_decoded_frame_);
break;
case EOS:
EXPECT_EQ(0, pending_decode_requests_);
ASSERT_EQ(VideoDecoder::kOk, last_decode_status_);
ASSERT_TRUE(last_decoded_frame_);
EXPECT_TRUE(last_decoded_frame_->end_of_stream());
break;
}
}
......@@ -123,11 +115,11 @@ class FakeVideoDecoderTest
current_config_,
base::TimeDelta::FromMilliseconds(kDurationMs * num_input_buffers_),
base::TimeDelta::FromMilliseconds(kDurationMs));
num_input_buffers_++;
} else {
buffer = DecoderBuffer::CreateEOSBuffer();
}
++num_input_buffers_;
++pending_decode_requests_;
decoder_->Decode(
......@@ -143,10 +135,10 @@ class FakeVideoDecoderTest
} while (!last_decoded_frame_ && pending_decode_requests_ == 0);
}
void ReadUntilEOS() {
void ReadAllFrames() {
do {
ReadOneFrame();
} while (last_decoded_frame_ && !last_decoded_frame_->end_of_stream());
Decode();
} while (num_input_buffers_ <= kTotalBuffers); // All input buffers + EOS.
}
void EnterPendingReadState() {
......@@ -247,7 +239,7 @@ TEST_P(FakeVideoDecoderTest, Initialize) {
TEST_P(FakeVideoDecoderTest, Read_AllFrames) {
Initialize();
ReadUntilEOS();
ReadAllFrames();
EXPECT_EQ(kTotalBuffers, num_decoded_frames_);
}
......@@ -342,7 +334,7 @@ TEST_P(FakeVideoDecoderTest, Reinitialize_FrameDropped) {
Initialize();
ReadOneFrame();
Initialize();
ReadUntilEOS();
ReadAllFrames();
EXPECT_LT(num_decoded_frames_, kTotalBuffers);
}
......
......@@ -213,34 +213,6 @@ void FFmpegAudioDecoder::DecodeBuffer(
DCHECK(buffer);
// During decode, because reads are issued asynchronously, it is possible to
// receive multiple end of stream buffers since each decode is acked. When the
// first end of stream buffer is read, FFmpeg may still have frames queued
// up in the decoder so we need to go through the decode loop until it stops
// giving sensible data. After that, the decoder should output empty
// frames. There are three states the decoder can be in:
//
// kNormal: This is the starting state. Buffers are decoded. Decode errors
// are discarded.
// kFlushCodec: There isn't any more input data. Call avcodec_decode_audio4
// until no more data is returned to flush out remaining
// frames. The input buffer is ignored at this point.
// kDecodeFinished: All calls return empty frames.
// kError: Unexpected error happened.
//
// These are the possible state transitions.
//
// kNormal -> kFlushCodec:
// When buffer->end_of_stream() is first true.
// kNormal -> kError:
// A decoding error occurs and decoding needs to stop.
// kFlushCodec -> kDecodeFinished:
// When avcodec_decode_audio4() returns 0 data.
// kFlushCodec -> kError:
// When avcodec_decode_audio4() errors out.
// (any state) -> kNormal:
// Any time Reset() is called.
// Make sure we are notified if http://crbug.com/49709 returns. Issue also
// occurs with some damaged files.
if (!buffer->end_of_stream() && buffer->timestamp() == kNoTimestamp()) {
......@@ -249,22 +221,28 @@ void FFmpegAudioDecoder::DecodeBuffer(
return;
}
if (!FFmpegDecode(buffer)) {
state_ = kError;
decode_cb.Run(kDecodeError);
return;
}
bool has_produced_frame;
do {
has_produced_frame = false;
if (!FFmpegDecode(buffer, &has_produced_frame)) {
state_ = kError;
decode_cb.Run(kDecodeError);
return;
}
// Repeat to flush the decoder after receiving EOS buffer.
} while (buffer->end_of_stream() && has_produced_frame);
if (buffer->end_of_stream()) {
if (buffer->end_of_stream())
state_ = kDecodeFinished;
output_cb_.Run(AudioBuffer::CreateEOSBuffer());
}
decode_cb.Run(kOk);
}
bool FFmpegAudioDecoder::FFmpegDecode(
const scoped_refptr<DecoderBuffer>& buffer) {
const scoped_refptr<DecoderBuffer>& buffer,
bool* has_produced_frame) {
DCHECK(!*has_produced_frame);
AVPacket packet;
av_init_packet(&packet);
if (buffer->end_of_stream()) {
......@@ -347,8 +325,8 @@ bool FFmpegAudioDecoder::FFmpegDecode(
const int decoded_frames = frame_decoded ? output->frame_count() : 0;
if (IsEndOfStream(result, decoded_frames, buffer)) {
DCHECK_EQ(packet.size, 0);
output_cb_.Run(AudioBuffer::CreateEOSBuffer());
} else if (discard_helper_->ProcessBuffers(buffer, output)) {
*has_produced_frame = true;
output_cb_.Run(output);
}
} while (packet.size > 0);
......
......@@ -45,6 +45,26 @@ class MEDIA_EXPORT FFmpegAudioDecoder : public AudioDecoder {
virtual void Stop() OVERRIDE;
private:
// There are four states the decoder can be in:
//
// - kUninitialized: The decoder is not initialized.
// - kNormal: This is the normal state. The decoder is idle and ready to
// decode input buffers, or is decoding an input buffer.
// - kDecodeFinished: EOS buffer received, codec flushed and decode finished.
// No further Decode() call should be made.
// - kError: Unexpected error happened.
//
// These are the possible state transitions.
//
// kUninitialized -> kNormal:
// The decoder is successfully initialized and is ready to decode buffers.
// kNormal -> kDecodeFinished:
// When buffer->end_of_stream() is true and avcodec_decode_audio4()
// returns 0 data.
// kNormal -> kError:
// A decoding error occurs and decoding needs to stop.
// (any state) -> kNormal:
// Any time Reset() is called.
enum DecoderState {
kUninitialized,
kNormal,
......@@ -58,7 +78,8 @@ class MEDIA_EXPORT FFmpegAudioDecoder : public AudioDecoder {
// Handles decoding an unencrypted encoded buffer.
void DecodeBuffer(const scoped_refptr<DecoderBuffer>& buffer,
const DecodeCB& decode_cb);
bool FFmpegDecode(const scoped_refptr<DecoderBuffer>& buffer);
bool FFmpegDecode(const scoped_refptr<DecoderBuffer>& buffer,
bool* has_produced_frame);
// Handles (re-)initializing the decoder with a (new) config.
// Returns true if initialization was successful.
......
......@@ -105,6 +105,7 @@ class FFmpegAudioDecoderTest : public testing::Test {
}
void OnDecoderOutput(const scoped_refptr<AudioBuffer>& buffer) {
EXPECT_FALSE(buffer->end_of_stream());
decoded_audio_.push_back(buffer);
}
......@@ -127,12 +128,6 @@ class FFmpegAudioDecoderTest : public testing::Test {
EXPECT_LT(i, decoded_audio_.size());
EXPECT_EQ(timestamp, decoded_audio_[i]->timestamp().InMicroseconds());
EXPECT_EQ(duration, decoded_audio_[i]->duration().InMicroseconds());
EXPECT_FALSE(decoded_audio_[i]->end_of_stream());
}
void ExpectEndOfStream(size_t i) {
EXPECT_LT(i, decoded_audio_.size());
EXPECT_TRUE(decoded_audio_[i]->end_of_stream());
}
base::MessageLoop message_loop_;
......@@ -173,10 +168,9 @@ TEST_F(FFmpegAudioDecoderTest, ProduceAudioSamples) {
ExpectDecodedAudio(1, 2902, 13061);
ExpectDecodedAudio(2, 15963, 23219);
// Call one more time to trigger EOS.
// Call one more time with EOS.
Decode();
ASSERT_EQ(5u, decoded_audio_.size());
ExpectEndOfStream(3);
ASSERT_EQ(3u, decoded_audio_.size());
Stop();
}
......
......@@ -189,7 +189,6 @@ void FFmpegVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
}
if (state_ == kDecodeFinished) {
output_cb_.Run(VideoFrame::CreateEOSFrame());
decode_cb_bound.Run(kOk);
return;
}
......@@ -228,10 +227,8 @@ void FFmpegVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
// Repeat to flush the decoder after receiving EOS buffer.
} while (buffer->end_of_stream() && has_produced_frame);
if (buffer->end_of_stream()) {
output_cb_.Run(VideoFrame::CreateEOSFrame());
if (buffer->end_of_stream())
state_ = kDecodeFinished;
}
decode_cb_bound.Run(kOk);
}
......
......@@ -62,7 +62,7 @@ class MEDIA_EXPORT FFmpegVideoDecoder : public VideoDecoder {
// Handles decoding an unencrypted encoded buffer.
bool FFmpegDecode(const scoped_refptr<DecoderBuffer>& buffer,
bool* produced_frame);
bool* has_produced_frame);
// Handles (re-)initializing the decoder with a (new) config.
// Returns true if initialization was successful.
......
......@@ -98,9 +98,7 @@ class FFmpegVideoDecoderTest : public testing::Test {
// decoding state.
void EnterDecodingState() {
EXPECT_EQ(VideoDecoder::kOk, DecodeSingleFrame(i_frame_buffer_));
ASSERT_EQ(2U, output_frames_.size());
EXPECT_FALSE(output_frames_[0]->end_of_stream());
EXPECT_TRUE(output_frames_[1]->end_of_stream());
ASSERT_EQ(1U, output_frames_.size());
}
// Sets up expectations and actions to put FFmpegVideoDecoder in an end
......@@ -108,30 +106,19 @@ class FFmpegVideoDecoderTest : public testing::Test {
void EnterEndOfStreamState() {
EXPECT_EQ(VideoDecoder::kOk, DecodeSingleFrame(end_of_stream_buffer_));
ASSERT_FALSE(output_frames_.empty());
EXPECT_TRUE(output_frames_.back()->end_of_stream());
}
typedef std::vector<scoped_refptr<DecoderBuffer> > InputBuffers;
typedef std::vector<scoped_refptr<VideoFrame> > OutputFrames;
// Decodes all buffers in |input_buffers| and push all successfully decoded
// output frames (excluding EOS frames) into |output_frames|.
// output frames into |output_frames|.
// Returns the last decode status returned by the decoder.
VideoDecoder::Status DecodeMultipleFrames(const InputBuffers& input_buffers) {
InputBuffers::const_iterator input_iter = input_buffers.begin();
while (output_frames_.empty() || !output_frames_.back()->end_of_stream()) {
// Prepare input buffer.
scoped_refptr<DecoderBuffer> buffer;
if (input_iter != input_buffers.end()) {
buffer = *input_iter;
++input_iter;
} else {
buffer = end_of_stream_buffer_;
}
VideoDecoder::Status status = Decode(buffer);
for (InputBuffers::const_iterator iter = input_buffers.begin();
iter != input_buffers.end();
++iter) {
VideoDecoder::Status status = Decode(*iter);
switch (status) {
case VideoDecoder::kOk:
break;
......@@ -143,7 +130,6 @@ class FFmpegVideoDecoderTest : public testing::Test {
return status;
}
}
return VideoDecoder::kOk;
}
......@@ -155,6 +141,7 @@ class FFmpegVideoDecoderTest : public testing::Test {
const scoped_refptr<DecoderBuffer>& buffer) {
InputBuffers input_buffers;
input_buffers.push_back(buffer);
input_buffers.push_back(end_of_stream_buffer_);
return DecodeMultipleFrames(input_buffers);
}
......@@ -171,12 +158,13 @@ class FFmpegVideoDecoderTest : public testing::Test {
InputBuffers input_buffers;
input_buffers.push_back(i_frame_buffer_);
input_buffers.push_back(buffer);
input_buffers.push_back(end_of_stream_buffer_);
VideoDecoder::Status status =
DecodeMultipleFrames(input_buffers);
EXPECT_EQ(VideoDecoder::kOk, status);
ASSERT_EQ(3U, output_frames_.size());
ASSERT_EQ(2U, output_frames_.size());
gfx::Size original_size = kVisibleRect.size();
EXPECT_EQ(original_size.width(),
......@@ -187,7 +175,6 @@ class FFmpegVideoDecoderTest : public testing::Test {
output_frames_[1]->visible_rect().size().width());
EXPECT_EQ(expected_height,
output_frames_[1]->visible_rect().size().height());
EXPECT_TRUE(output_frames_[2]->end_of_stream());
}
VideoDecoder::Status Decode(const scoped_refptr<DecoderBuffer>& buffer) {
......@@ -202,6 +189,7 @@ class FFmpegVideoDecoderTest : public testing::Test {
}
void FrameReady(const scoped_refptr<VideoFrame>& frame) {
DCHECK(!frame->end_of_stream());
output_frames_.push_back(frame);
}
......@@ -338,9 +326,7 @@ TEST_F(FFmpegVideoDecoderTest, DecodeFrame_Normal) {
// Simulate decoding a single frame.
EXPECT_EQ(VideoDecoder::kOk, DecodeSingleFrame(i_frame_buffer_));
ASSERT_EQ(2U, output_frames_.size());
EXPECT_FALSE(output_frames_[0]->end_of_stream());
EXPECT_TRUE(output_frames_[1]->end_of_stream());
ASSERT_EQ(1U, output_frames_.size());
}
// Verify current behavior for 0 byte frames. FFmpeg simply ignores
......@@ -354,15 +340,12 @@ TEST_F(FFmpegVideoDecoderTest, DecodeFrame_0ByteFrame) {
input_buffers.push_back(i_frame_buffer_);
input_buffers.push_back(zero_byte_buffer);
input_buffers.push_back(i_frame_buffer_);
input_buffers.push_back(end_of_stream_buffer_);
VideoDecoder::Status status = DecodeMultipleFrames(input_buffers);
EXPECT_EQ(VideoDecoder::kOk, status);
ASSERT_EQ(3U, output_frames_.size());
EXPECT_FALSE(output_frames_[0]->end_of_stream());
EXPECT_FALSE(output_frames_[1]->end_of_stream());
EXPECT_TRUE(output_frames_[2]->end_of_stream());
ASSERT_EQ(2U, output_frames_.size());
}
TEST_F(FFmpegVideoDecoderTest, DecodeFrame_DecodeError) {
......@@ -391,8 +374,6 @@ TEST_F(FFmpegVideoDecoderTest, DecodeFrame_DecodeErrorAtEndOfStream) {
Initialize();
EXPECT_EQ(VideoDecoder::kOk, DecodeSingleFrame(corrupt_i_frame_buffer_));
ASSERT_FALSE(output_frames_.empty());
EXPECT_TRUE(output_frames_.back()->end_of_stream());
}
// Decode |i_frame_buffer_| and then a frame with a larger width and verify
......
......@@ -148,7 +148,7 @@ static void ReportGpuVideoDecoderInitializeStatusToUMAAndRunCB(
}
void GpuVideoDecoder::Initialize(const VideoDecoderConfig& config,
bool live_mode,
bool /* low_delay */,
const PipelineStatusCB& orig_status_cb,
const OutputCB& output_cb) {
DVLOG(3) << "Initialize()";
......@@ -581,7 +581,6 @@ void GpuVideoDecoder::NotifyFlushDone() {
DCheckGpuVideoAcceleratorFactoriesTaskRunnerIsCurrent();
DCHECK_EQ(state_, kDrainingDecoder);
state_ = kDecoderDrained;
DeliverFrame(VideoFrame::CreateEOSFrame());
base::ResetAndReturn(&eos_decode_cb_).Run(kOk);
}
......
......@@ -43,7 +43,7 @@ class MEDIA_EXPORT GpuVideoDecoder
// VideoDecoder implementation.
virtual void Initialize(const VideoDecoderConfig& config,
bool live_mode,
bool low_delay,
const PipelineStatusCB& status_cb,
const OutputCB& output_cb) OVERRIDE;
virtual void Decode(const scoped_refptr<DecoderBuffer>& buffer,
......
......@@ -305,7 +305,6 @@ void OpusAudioDecoder::DecodeBuffer(
// Libopus does not buffer output. Decoding is complete when an end of stream
// input buffer is received.
if (input->end_of_stream()) {
output_cb_.Run(AudioBuffer::CreateEOSBuffer());
decode_cb.Run(kOk);
return;
}
......
......@@ -126,11 +126,6 @@ class OpusAudioDecoderTest : public testing::Test {
EXPECT_FALSE(decoded_audio_[i]->end_of_stream());
}
void ExpectEndOfStream(size_t i) {
EXPECT_LT(i, decoded_audio_.size());
EXPECT_TRUE(decoded_audio_[i]->end_of_stream());
}
size_t decoded_audio_size() const {
return decoded_audio_.size();
}
......@@ -186,10 +181,9 @@ TEST_F(OpusAudioDecoderTest, ProduceAudioSamples) {
ExpectDecodedAudio(1, 3500, 10000);
ExpectDecodedAudio(2, 13500, 10000);
// Call one more time to trigger EOS.
// Call one more time with EOS.
SendEndOfStream();
ASSERT_EQ(4u, decoded_audio_size());
ExpectEndOfStream(3);
ASSERT_EQ(3u, decoded_audio_size());
Stop();
}
......
......@@ -179,6 +179,15 @@ class VideoFrameStreamTest
} while (!pending_read_);
}
void ReadAllFrames() {
do {
ReadOneFrame();
} while (frame_read_.get() && !frame_read_->end_of_stream());
const int total_num_frames = kNumConfigs * kNumBuffersInOneConfig;
DCHECK_EQ(num_decoded_frames_, total_num_frames);
}
enum PendingState {
NOT_PENDING,
DEMUXER_READ_NORMAL,
......@@ -371,12 +380,7 @@ TEST_P(VideoFrameStreamTest, ReadOneFrame) {
TEST_P(VideoFrameStreamTest, ReadAllFrames) {
Initialize();
do {
Read();
} while (frame_read_.get() && !frame_read_->end_of_stream());
const int total_num_frames = kNumConfigs * kNumBuffersInOneConfig;
DCHECK_EQ(num_decoded_frames_, total_num_frames);
ReadAllFrames();
}
TEST_P(VideoFrameStreamTest, Read_AfterReset) {
......@@ -536,6 +540,15 @@ TEST_P(VideoFrameStreamTest, Reset_AfterDemuxerRead_ConfigChange) {
Read();
}
TEST_P(VideoFrameStreamTest, Reset_AfterEndOfStream) {
Initialize();
ReadAllFrames();
Reset();
num_decoded_frames_ = 0;
demuxer_stream_->SeekToStart();
ReadAllFrames();
}
TEST_P(VideoFrameStreamTest, Reset_DuringNoKeyRead) {
Initialize();
EnterPendingState(DECRYPTOR_NO_KEY);
......
......@@ -174,10 +174,9 @@ class VideoRendererImplTest : public ::testing::Test {
// nn - Queue a decoder buffer with timestamp nn * 1000us
// abort - Queue an aborted read
// error - Queue a decoder error
// eos - Queue an end of stream decoder buffer
//
// Examples:
// A clip that is four frames long: "0 10 20 30 eos"
// A clip that is four frames long: "0 10 20 30"
// A clip that has a decode error: "60 70 error"
void QueueFrames(const std::string& str) {
std::vector<std::string> tokens;
......@@ -197,12 +196,6 @@ class VideoRendererImplTest : public ::testing::Test {
continue;
}
if (tokens[i] == "eos") {
decode_results_.push_back(
std::make_pair(VideoDecoder::kOk, VideoFrame::CreateEOSFrame()));
continue;
}
int timestamp_in_ms = 0;
if (base::StringToInt(tokens[i], &timestamp_in_ms)) {
gfx::Size natural_size = TestVideoConfig::NormalCodedSize();
......@@ -263,6 +256,26 @@ class VideoRendererImplTest : public ::testing::Test {
decode_results_.pop_front();
}
void SatisfyPendingReadWithEndOfStream() {
DCHECK(!decode_cb_.is_null());
// Return EOS buffer to trigger EOS frame.
EXPECT_CALL(demuxer_stream_, Read(_))
.WillOnce(RunCallback<0>(DemuxerStream::kOk,
DecoderBuffer::CreateEOSBuffer()));
// Satify pending |decode_cb_| to trigger a new DemuxerStream::Read().
message_loop_.PostTask(
FROM_HERE,
base::Bind(base::ResetAndReturn(&decode_cb_), VideoDecoder::kOk));
WaitForPendingRead();
message_loop_.PostTask(
FROM_HERE,
base::Bind(base::ResetAndReturn(&decode_cb_), VideoDecoder::kOk));
}
void AdvanceTimeInMs(int time_ms) {
DCHECK_EQ(&message_loop_, base::MessageLoop::current());
base::AutoLock l(lock_);
......@@ -414,10 +427,12 @@ TEST_F(VideoRendererImplTest, EndOfStream_ClipDuration) {
// Next frame has timestamp way past duration. Its timestamp will be adjusted
// to match the duration of the video.
QueueFrames(base::IntToString(kVideoDurationInMs + 1000));
SatisfyPendingRead();
WaitForPendingRead();
// Queue the end of stream frame and wait for the last frame to be rendered.
QueueFrames("eos");
SatisfyPendingRead();
SatisfyPendingReadWithEndOfStream();
EXPECT_CALL(mock_display_cb_, Display(HasTimestamp(kVideoDurationInMs)));
AdvanceTimeInMs(kVideoDurationInMs);
WaitForEnded();
......
......@@ -259,8 +259,9 @@ static vpx_codec_ctx* InitializeVpxContext(vpx_codec_ctx* context,
bool VpxVideoDecoder::ConfigureDecoder(const VideoDecoderConfig& config) {
if (config.codec() != kCodecVP8 && config.codec() != kCodecVP9)
return false;
// Only VP8 videos with alpha are handled by VpxVideoDecoder. Everything else
// goes to FFmpegVideoDecoder.
// In VP8 videos, only those with alpha are handled by VpxVideoDecoder. All
// other VP8 videos go to FFmpegVideoDecoder.
if (config.codec() == kCodecVP8 && config.format() != VideoFrame::YV12A)
return false;
......@@ -354,7 +355,6 @@ void VpxVideoDecoder::DecodeBuffer(const scoped_refptr<DecoderBuffer>& buffer) {
// Transition to kDecodeFinished on the first end of stream buffer.
if (state_ == kNormal && buffer->end_of_stream()) {
state_ = kDecodeFinished;
output_cb_.Run(VideoFrame::CreateEOSFrame());
base::ResetAndReturn(&decode_cb_).Run(kOk);
return;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment