FFMPEG-Plugin: Implement decoding of streamed video data.
- This commit makes the mpeg2_decoder_test successfully decode the test video
into 84 consecutive PNG images, yeah :)
- If this commit broke playing video files for you please file a bug report.
I've tested only with one video file (big_buck_bunny_720p_stereo.ogg) that
everything still works.
- The implementation has some shortcomings though, that will be addressed with
some later commits:
1. Start time of media header is wrongly calculated. At the moment we are
using the start time of the first encoded data chunk we read via
GetNextChunk(). This works only for chunk that contain one and exactly
one frame, but not for chunks that contain the end or middle of a frame.
2. Fields of the media header aren't updated when there is a format change
in the middle of the video stream (for example the pixel aspect ratio
might change in the middle of a DVB video stream (e.g. switch from 4:3
to 16:9)).
- Also fix a potential bug, where the CODEC_FLAG_TRUNCATED flag was always
set, due to missing brackets.
Signed-off-by: Colin Günther <[email protected]>
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
#include <new>
|
#include <new>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <Bitmap.h>
|
#include <Bitmap.h>
|
||||||
@@ -251,8 +252,12 @@ AVCodecDecoder::SeekedTo(int64 frame, bigtime_t time)
|
|||||||
{
|
{
|
||||||
status_t ret = B_OK;
|
status_t ret = B_OK;
|
||||||
// Reset the FFmpeg codec to flush buffers, so we keep the sync
|
// Reset the FFmpeg codec to flush buffers, so we keep the sync
|
||||||
if (fCodecInitDone)
|
if (fCodecInitDone) {
|
||||||
avcodec_flush_buffers(fContext);
|
avcodec_flush_buffers(fContext);
|
||||||
|
av_init_packet(&fTempPacket);
|
||||||
|
fTempPacket.size = 0;
|
||||||
|
fTempPacket.data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// Flush internal buffers as well.
|
// Flush internal buffers as well.
|
||||||
fChunkBuffer = NULL;
|
fChunkBuffer = NULL;
|
||||||
@@ -430,10 +435,11 @@ AVCodecDecoder::_NegotiateVideoOutputFormat(media_format* inOutFormat)
|
|||||||
fContext->extradata = (uint8_t*)fExtraData;
|
fContext->extradata = (uint8_t*)fExtraData;
|
||||||
fContext->extradata_size = fExtraDataSize;
|
fContext->extradata_size = fExtraDataSize;
|
||||||
|
|
||||||
if (fCodec->capabilities & CODEC_CAP_TRUNCATED)
|
if (fCodec->capabilities & CODEC_CAP_TRUNCATED) {
|
||||||
// Expect and handle video frames to be splitted across consecutive
|
// Expect and handle video frames to be splitted across consecutive
|
||||||
// data chunks.
|
// data chunks.
|
||||||
fContext->flags |= CODEC_FLAG_TRUNCATED;
|
fContext->flags |= CODEC_FLAG_TRUNCATED;
|
||||||
|
}
|
||||||
|
|
||||||
TRACE(" requested video format 0x%x\n",
|
TRACE(" requested video format 0x%x\n",
|
||||||
inOutFormat->u.raw_video.display.format);
|
inOutFormat->u.raw_video.display.format);
|
||||||
@@ -516,6 +522,10 @@ AVCodecDecoder::_NegotiateVideoOutputFormat(media_format* inOutFormat)
|
|||||||
inOutFormat->require_flags = 0;
|
inOutFormat->require_flags = 0;
|
||||||
inOutFormat->deny_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
|
inOutFormat->deny_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
|
||||||
|
|
||||||
|
av_init_packet(&fTempPacket);
|
||||||
|
fTempPacket.size = 0;
|
||||||
|
fTempPacket.data = NULL;
|
||||||
|
|
||||||
#ifdef TRACE_AV_CODEC
|
#ifdef TRACE_AV_CODEC
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
string_for_format(*inOutFormat, buffer, sizeof(buffer));
|
string_for_format(*inOutFormat, buffer, sizeof(buffer));
|
||||||
@@ -700,16 +710,26 @@ AVCodecDecoder::_DecodeVideo(void* outBuffer, int64* outFrameCount,
|
|||||||
status_t
|
status_t
|
||||||
AVCodecDecoder::_DecodeNextVideoFrame()
|
AVCodecDecoder::_DecodeNextVideoFrame()
|
||||||
{
|
{
|
||||||
|
assert(fTempPacket.size >= 0);
|
||||||
|
|
||||||
bool firstRun = true;
|
bool firstRun = true;
|
||||||
while (true) {
|
while (true) {
|
||||||
media_header chunkMediaHeader;
|
media_header chunkMediaHeader;
|
||||||
status_t err = GetNextChunk(&fChunkBuffer, &fChunkBufferSize,
|
|
||||||
&chunkMediaHeader);
|
if (fTempPacket.size == 0) {
|
||||||
if (err != B_OK) {
|
// Our packet buffer is empty, so fill it now.
|
||||||
TRACE("AVCodecDecoder::_DecodeVideo(): error from "
|
status_t getNextChunkStatus = GetNextChunk(&fChunkBuffer,
|
||||||
|
&fChunkBufferSize, &chunkMediaHeader);
|
||||||
|
if (getNextChunkStatus != B_OK) {
|
||||||
|
TRACE("AVCodecDecoder::_DecodeNextVideoFrame(): error from "
|
||||||
"GetNextChunk(): %s\n", strerror(err));
|
"GetNextChunk(): %s\n", strerror(err));
|
||||||
return err;
|
return getNextChunkStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fTempPacket.data = static_cast<uint8_t*>(const_cast<void*>(
|
||||||
|
fChunkBuffer));
|
||||||
|
fTempPacket.size = fChunkBufferSize;
|
||||||
|
|
||||||
#ifdef LOG_STREAM_TO_FILE
|
#ifdef LOG_STREAM_TO_FILE
|
||||||
if (sDumpedPackets < 100) {
|
if (sDumpedPackets < 100) {
|
||||||
sStreamLogFile.Write(fChunkBuffer, fChunkBufferSize);
|
sStreamLogFile.Write(fChunkBuffer, fChunkBufferSize);
|
||||||
@@ -718,6 +738,7 @@ AVCodecDecoder::_DecodeNextVideoFrame()
|
|||||||
} else if (sDumpedPackets == 100)
|
} else if (sDumpedPackets == 100)
|
||||||
sStreamLogFile.Unset();
|
sStreamLogFile.Unset();
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
if (firstRun) {
|
if (firstRun) {
|
||||||
firstRun = false;
|
firstRun = false;
|
||||||
@@ -746,24 +767,28 @@ AVCodecDecoder::_DecodeNextVideoFrame()
|
|||||||
bigtime_t startTime = system_time();
|
bigtime_t startTime = system_time();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// NOTE: In the FFmpeg code example I've read, the length returned by
|
// NOTE: In the FFMPEG 0.10.2 code example decoding_encoding.c, the
|
||||||
// avcodec_decode_video() is completely ignored. Furthermore, the
|
// length returned by avcodec_decode_video2() is used to update the
|
||||||
// packet buffers are supposed to contain complete frames only so we
|
// packet buffer size (here it is fTempPacket.size). This way the
|
||||||
// don't seem to be required to buffer any packets because not the
|
// packet buffer is allowed to contain incomplete frames so we are
|
||||||
// complete packet has been read.
|
// required to buffer the packets between different calls to
|
||||||
fTempPacket.data = (uint8_t*)fChunkBuffer;
|
// _DecodeNextVideoFrame().
|
||||||
fTempPacket.size = fChunkBufferSize;
|
|
||||||
int gotPicture = 0;
|
int gotPicture = 0;
|
||||||
int len = avcodec_decode_video2(fContext, fRawDecodedPicture,
|
int decodedDataSizeInBytes = avcodec_decode_video2(fContext,
|
||||||
&gotPicture, &fTempPacket);
|
fRawDecodedPicture, &gotPicture, &fTempPacket);
|
||||||
if (len < 0) {
|
if (decodedDataSizeInBytes < 0) {
|
||||||
TRACE("[v] AVCodecDecoder: error in decoding frame %lld: %d\n",
|
TRACE("[v] AVCodecDecoder: ignoring error in decoding frame %lld:"
|
||||||
fFrame, len);
|
" %d\n", fFrame, len);
|
||||||
// NOTE: An error from avcodec_decode_video() seems to be ignored
|
// NOTE: An error from avcodec_decode_video2() is ignored by the
|
||||||
// in the ffplay sample code.
|
// FFMPEG 0.10.2 example decoding_encoding.c. Only the packet
|
||||||
// return B_ERROR;
|
// buffers are flushed accordingly
|
||||||
|
fTempPacket.data = NULL;
|
||||||
|
fTempPacket.size = 0;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fTempPacket.size -= decodedDataSizeInBytes;
|
||||||
|
fTempPacket.data += decodedDataSizeInBytes;
|
||||||
|
|
||||||
//TRACE("FFDEC: PTS = %d:%d:%d.%d - fContext->frame_number = %ld "
|
//TRACE("FFDEC: PTS = %d:%d:%d.%d - fContext->frame_number = %ld "
|
||||||
// "fContext->frame_rate = %ld\n", (int)(fContext->pts / (60*60*1000000)),
|
// "fContext->frame_rate = %ld\n", (int)(fContext->pts / (60*60*1000000)),
|
||||||
|
|||||||
Reference in New Issue
Block a user