* Copyright (C) 2001 Carlos Hasan
* Copyright (C) 2001 François Revol
* Copyright (C) 2001 Axel Dörfler
* Copyright (C) 2004 Marcus Overhagen
* Copyright (C) 2009 Stephan Amßus <superstippi@gmx.de>
* Copyright (C) 2014 Colin Günther <coling@gmx.de>
* Copyright (C) 2015 Adrien Destugues <pulkomandy@pulkomandy.tk>
*
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "AVCodecDecoder.h"
#include <new>
#include <assert.h>
#include <string.h>
#include <Bitmap.h>
#include <Debug.h>
#include <OS.h>
#include <String.h>
#include "Utilities.h"
#undef TRACE
#ifdef TRACE_AV_CODEC
# define TRACE(x...) printf(x)
# define TRACE_AUDIO(x...) printf(x)
# define TRACE_VIDEO(x...) printf(x)
#else
# define TRACE(x...)
# define TRACE_AUDIO(x...)
# define TRACE_VIDEO(x...)
#endif
#ifdef LOG_STREAM_TO_FILE
# include <File.h>
static BFile sAudioStreamLogFile(
"/boot/home/Desktop/AVCodecDebugAudioStream.raw",
B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
static BFile sVideoStreamLogFile(
"/boot/home/Desktop/AVCodecDebugVideoStream.raw",
B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
static int sDumpedPackets = 0;
#endif
typedef AVCodecID CodecID;
struct wave_format_ex {
uint16 format_tag;
uint16 channels;
uint32 frames_per_sec;
uint32 avg_bytes_per_sec;
uint16 block_align;
uint16 bits_per_sample;
uint16 extra_size;
} _PACKED;
struct avformat_codec_context {
int sample_rate;
int channels;
};
#define DO_PROFILING 0
#if DO_PROFILING
static bigtime_t decodingTime = 0;
static bigtime_t conversionTime = 0;
static long profileCounter = 0;
#endif
AVCodecDecoder::AVCodecDecoder()
:
fHeader(),
fInputFormat(),
fFrame(0),
fIsAudio(false),
fCodec(NULL),
fCodecContext(NULL),
fResampleContext(NULL),
fDecodedData(NULL),
fDecodedDataSizeInBytes(0),
fPostProcessedDecodedPicture(av_frame_alloc()),
fRawDecodedPicture(av_frame_alloc()),
fRawDecodedAudio(av_frame_alloc()),
fCodecInitDone(false),
#if USE_SWS_FOR_COLOR_SPACE_CONVERSION
fSwsContext(NULL),
#else
fFormatConversionFunc(NULL),
#endif
fExtraData(NULL),
fExtraDataSize(0),
fBlockAlign(0),
fOutputColorSpace(B_NO_COLOR_SPACE),
fOutputFrameCount(0),
fOutputFrameRate(1.0),
fOutputFrameSize(0),
fInputFrameSize(0),
fChunkBuffer(NULL),
fChunkBufferSize(0),
fAudioDecodeError(false),
fDecodedDataBuffer(av_frame_alloc()),
fDecodedDataBufferOffset(0),
fDecodedDataBufferSize(0),
fTempPacket(NULL),
fBufferSinkContext(NULL),
fBufferSourceContext(NULL),
fFilterGraph(NULL),
fFilterFrame(NULL)
{
TRACE("AVCodecDecoder::AVCodecDecoder()\n");
}
AVCodecDecoder::~AVCodecDecoder()
{
TRACE("[%c] AVCodecDecoder::~AVCodecDecoder()\n", fIsAudio ? 'a' : 'v');
#if DO_PROFILING
if (profileCounter > 0) {
printf("[%c] profile: d1 = %lld, d2 = %lld (%lld)\n",
fIsAudio ? 'a' : 'v', decodingTime / profileCounter,
conversionTime / profileCounter, fFrame);
}
#endif
swr_free(&fResampleContext);
free(fChunkBuffer);
free(fDecodedData);
av_frame_free(&fPostProcessedDecodedPicture);
av_frame_free(&fRawDecodedPicture);
av_free(fRawDecodedAudio->opaque);
av_frame_free(&fRawDecodedAudio);
if (fCodecContext != NULL) {
fCodecContext->extradata = NULL;
avcodec_free_context(&fCodecContext);
}
av_frame_free(&fDecodedDataBuffer);
av_frame_free(&fFilterFrame);
avfilter_graph_free(&fFilterGraph);
#if USE_SWS_FOR_COLOR_SPACE_CONVERSION
if (fSwsContext != NULL)
sws_freeContext(fSwsContext);
#endif
delete[] fExtraData;
av_packet_free(&fTempPacket);
}
void
AVCodecDecoder::GetCodecInfo(media_codec_info* mci)
{
snprintf(mci->short_name, 32, "%s", fCodec->name);
snprintf(mci->pretty_name, 96, "%s", fCodec->long_name);
mci->id = 0;
mci->sub_id = fCodec->id;
}
status_t
AVCodecDecoder::Setup(media_format* ioEncodedFormat, const void* infoBuffer,
size_t infoSize)
{
if (ioEncodedFormat->type != B_MEDIA_ENCODED_AUDIO
&& ioEncodedFormat->type != B_MEDIA_ENCODED_VIDEO)
return B_ERROR;
fIsAudio = (ioEncodedFormat->type == B_MEDIA_ENCODED_AUDIO);
TRACE("[%c] AVCodecDecoder::Setup()\n", fIsAudio ? 'a' : 'v');
#ifdef TRACE_AV_CODEC
char buffer[1024];
string_for_format(*ioEncodedFormat, buffer, sizeof(buffer));
TRACE("[%c] input_format = %s\n", fIsAudio ? 'a' : 'v', buffer);
TRACE("[%c] infoSize = %ld\n", fIsAudio ? 'a' : 'v', infoSize);
TRACE("[%c] user_data_type = %08" B_PRIx32 "\n", fIsAudio ? 'a' : 'v',
ioEncodedFormat->user_data_type);
TRACE("[%c] meta_data_size = %" B_PRId32 "\n", fIsAudio ? 'a' : 'v',
ioEncodedFormat->MetaDataSize());
#endif
media_format_description description;
if (BMediaFormats().GetCodeFor(*ioEncodedFormat,
B_MISC_FORMAT_FAMILY, &description) == B_OK) {
if (description.u.misc.file_format != 'ffmp')
return B_NOT_SUPPORTED;
fCodec = avcodec_find_decoder(static_cast<CodecID>(
description.u.misc.codec));
if (fCodec == NULL) {
TRACE(" unable to find the correct FFmpeg decoder (id = %" B_PRIu32 ")\n",
description.u.misc.codec);
return B_ERROR;
}
TRACE(" found decoder %s\n", fCodec->name);
const void* extraData = infoBuffer;
fExtraDataSize = infoSize;
if (description.family == B_WAV_FORMAT_FAMILY
&& infoSize >= sizeof(wave_format_ex)) {
TRACE(" trying to use wave_format_ex\n");
const wave_format_ex* waveFormatData
= (const wave_format_ex*)infoBuffer;
size_t waveFormatSize = infoSize;
if (waveFormatData != NULL && waveFormatSize > 0) {
fBlockAlign = waveFormatData->block_align;
TRACE(" found block align: %d\n", fBlockAlign);
fExtraDataSize = waveFormatData->extra_size;
extraData = waveFormatData + 1;
}
} else {
if (fIsAudio) {
fBlockAlign
= ioEncodedFormat->u.encoded_audio.output.buffer_size;
TRACE(" using buffer_size as block align: %d\n",
fBlockAlign);
}
}
if (extraData != NULL && fExtraDataSize > 0) {
TRACE("AVCodecDecoder: extra data size %ld\n", infoSize);
delete[] fExtraData;
fExtraData = new(std::nothrow) char[fExtraDataSize];
if (fExtraData != NULL)
memcpy(fExtraData, infoBuffer, fExtraDataSize);
else
fExtraDataSize = 0;
}
fInputFormat = *ioEncodedFormat;
return B_OK;
} else {
TRACE("AVCodecDecoder: BMediaFormats().GetCodeFor() failed.\n");
}
printf("AVCodecDecoder::Setup failed!\n");
return B_ERROR;
}
status_t
AVCodecDecoder::SeekedTo(int64 frame, bigtime_t time)
{
status_t ret = B_OK;
if (fCodecInitDone) {
avcodec_flush_buffers(fCodecContext);
_ResetTempPacket();
}
free(fChunkBuffer);
fChunkBuffer = NULL;
fChunkBufferSize = 0;
fDecodedDataBufferOffset = 0;
fDecodedDataBufferSize = 0;
fDecodedDataSizeInBytes = 0;
fFrame = frame;
return ret;
}
status_t
AVCodecDecoder::NegotiateOutputFormat(media_format* inOutFormat)
{
TRACE("AVCodecDecoder::NegotiateOutputFormat() [%c] \n",
fIsAudio ? 'a' : 'v');
#ifdef TRACE_AV_CODEC
char buffer[1024];
string_for_format(*inOutFormat, buffer, sizeof(buffer));
TRACE(" [%c] requested format = %s\n", fIsAudio ? 'a' : 'v', buffer);
#endif
if (fCodecContext != NULL) {
fCodecContext->extradata = NULL;
avcodec_free_context(&fCodecContext);
}
fCodecContext = avcodec_alloc_context3(fCodec);
fCodecInitDone = false;
system_info info;
get_system_info(&info);
fCodecContext->err_recognition = AV_EF_CAREFUL;
fCodecContext->error_concealment = 3;
fCodecContext->thread_count = info.cpu_count;
if (fIsAudio)
return _NegotiateAudioOutputFormat(inOutFormat);
else
return _NegotiateVideoOutputFormat(inOutFormat);
}
status_t
AVCodecDecoder::Decode(void* outBuffer, int64* outFrameCount,
media_header* mediaHeader, media_decode_info* info)
{
if (!fCodecInitDone)
return B_NO_INIT;
status_t ret;
if (fIsAudio)
ret = _DecodeAudio(outBuffer, outFrameCount, mediaHeader, info);
else
ret = _DecodeVideo(outBuffer, outFrameCount, mediaHeader, info);
return ret;
}
void
AVCodecDecoder::_ResetTempPacket()
{
if (fTempPacket == NULL)
fTempPacket = av_packet_alloc();
fTempPacket->size = 0;
fTempPacket->data = NULL;
}
static int
get_channel_count(AVCodecContext* context)
{
return context->ch_layout.nb_channels;
}
static void
set_channel_count(AVCodecContext* context, int count)
{
context->ch_layout.nb_channels = count;
}
status_t
AVCodecDecoder::_NegotiateAudioOutputFormat(media_format* inOutFormat)
{
TRACE("AVCodecDecoder::_NegotiateAudioOutputFormat()\n");
_ApplyEssentialAudioContainerPropertiesToContext();
if (avcodec_open2(fCodecContext, fCodec, NULL) < 0) {
TRACE("avcodec_open() failed to init codec!\n");
return B_ERROR;
}
fCodecInitDone = true;
free(fChunkBuffer);
fChunkBuffer = NULL;
fChunkBufferSize = 0;
fAudioDecodeError = false;
fDecodedDataBufferOffset = 0;
fDecodedDataBufferSize = 0;
_ResetTempPacket();
status_t statusOfDecodingFirstFrameChunk = _DecodeNextAudioFrameChunk();
if (statusOfDecodingFirstFrameChunk != B_OK) {
TRACE("[a] decoding first audio frame chunk failed\n");
return B_ERROR;
}
media_multi_audio_format outputAudioFormat;
outputAudioFormat = media_raw_audio_format::wildcard;
outputAudioFormat.byte_order = B_MEDIA_HOST_ENDIAN;
outputAudioFormat.frame_rate = fCodecContext->sample_rate;
outputAudioFormat.channel_count = get_channel_count(fCodecContext);
ConvertAVSampleFormatToRawAudioFormat(fCodecContext->sample_fmt,
outputAudioFormat.format);
if (outputAudioFormat.format == 0) {
TRACE(" format still a wild-card, assuming B_AUDIO_SHORT.\n");
outputAudioFormat.format = media_raw_audio_format::B_AUDIO_SHORT;
}
outputAudioFormat.buffer_size = inOutFormat->u.raw_audio.buffer_size;
size_t sampleSize = outputAudioFormat.format
& media_raw_audio_format::B_AUDIO_SIZE_MASK;
if (outputAudioFormat.buffer_size == 0) {
outputAudioFormat.buffer_size = 512 * sampleSize
* outputAudioFormat.channel_count;
}
inOutFormat->type = B_MEDIA_RAW_AUDIO;
inOutFormat->u.raw_audio = outputAudioFormat;
inOutFormat->require_flags = 0;
inOutFormat->deny_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
fOutputFrameSize = sampleSize * outputAudioFormat.channel_count;
fOutputFrameCount = outputAudioFormat.buffer_size / fOutputFrameSize;
fOutputFrameRate = outputAudioFormat.frame_rate;
if (av_sample_fmt_is_planar(fCodecContext->sample_fmt))
fInputFrameSize = sampleSize;
else
fInputFrameSize = fOutputFrameSize;
fRawDecodedAudio->opaque
= av_realloc(fRawDecodedAudio->opaque, sizeof(avformat_codec_context));
if (fRawDecodedAudio->opaque == NULL)
return B_NO_MEMORY;
if (av_sample_fmt_is_planar(fCodecContext->sample_fmt)) {
fResampleContext = NULL;
swr_alloc_set_opts2(&fResampleContext,
&fCodecContext->ch_layout,
fCodecContext->request_sample_fmt,
fCodecContext->sample_rate,
&fCodecContext->ch_layout,
fCodecContext->sample_fmt,
fCodecContext->sample_rate,
0, NULL);
swr_init(fResampleContext);
}
TRACE(" bit_rate = %" PRId64 ", sample_rate = %d, channels = %d, "
"output frame size: %d, count: %" B_PRId32 ", rate: %.2f\n",
fCodecContext->bit_rate, fCodecContext->sample_rate, fCodecContext->ch_layout.nb_channels,
fOutputFrameSize, fOutputFrameCount, fOutputFrameRate);
return B_OK;
}
status_t
AVCodecDecoder::_NegotiateVideoOutputFormat(media_format* inOutFormat)
{
TRACE("AVCodecDecoder::_NegotiateVideoOutputFormat()\n");
TRACE(" requested video format 0x%x\n",
inOutFormat->u.raw_video.display.format);
_ApplyEssentialVideoContainerPropertiesToContext();
if (avcodec_open2(fCodecContext, fCodec, NULL) < 0) {
TRACE("avcodec_open() failed to init codec!\n");
return B_ERROR;
}
fCodecInitDone = true;
#if USE_SWS_FOR_COLOR_SPACE_CONVERSION
fOutputColorSpace = B_RGB32;
#else
if (inOutFormat->u.raw_video.display.format == B_YCbCr422)
fOutputColorSpace = B_YCbCr422;
else
fOutputColorSpace = B_RGB32;
#endif
#if USE_SWS_FOR_COLOR_SPACE_CONVERSION
if (fSwsContext != NULL)
sws_freeContext(fSwsContext);
fSwsContext = NULL;
#else
fFormatConversionFunc = 0;
#endif
free(fChunkBuffer);
fChunkBuffer = NULL;
fChunkBufferSize = 0;
_ResetTempPacket();
status_t statusOfDecodingFirstFrame = _DecodeNextVideoFrame();
if (statusOfDecodingFirstFrame != B_OK) {
TRACE("[v] decoding first video frame failed\n");
return B_ERROR;
}
#if USE_SWS_FOR_COLOR_SPACE_CONVERSION
if (fSwsContext == NULL) {
TRACE("No SWS Scale context or decoder has not set the pixel format "
"yet!\n");
}
#else
if (fFormatConversionFunc == NULL) {
TRACE("no pixel format conversion function found or decoder has "
"not set the pixel format yet!\n");
}
#endif
inOutFormat->type = B_MEDIA_RAW_VIDEO;
inOutFormat->require_flags = 0;
inOutFormat->deny_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
inOutFormat->u.raw_video = fInputFormat.u.encoded_video.output;
inOutFormat->u.raw_video.interlace = 1;
inOutFormat->u.raw_video.first_active
= fHeader.u.raw_video.first_active_line;
inOutFormat->u.raw_video.last_active = fHeader.u.raw_video.line_count;
inOutFormat->u.raw_video.pixel_width_aspect
= fHeader.u.raw_video.pixel_width_aspect;
inOutFormat->u.raw_video.pixel_height_aspect
= fHeader.u.raw_video.pixel_height_aspect;
float fromFormat = fInputFormat.u.encoded_video.output.field_rate;
if (fromFormat < 70)
inOutFormat->u.raw_video.field_rate = fromFormat;
AVRational codec_fr = fCodecContext->framerate;
if (codec_fr.num > 0 && codec_fr.den > 0
&& (fromFormat == 0 || av_q2d(codec_fr) < fromFormat * 0.7)) {
inOutFormat->u.raw_video.field_rate = av_q2d(fCodecContext->framerate);
}
inOutFormat->u.raw_video.display.format = fOutputColorSpace;
inOutFormat->u.raw_video.display.line_width
= fHeader.u.raw_video.display_line_width;
inOutFormat->u.raw_video.display.line_count
= fHeader.u.raw_video.display_line_count;
inOutFormat->u.raw_video.display.bytes_per_row
= fHeader.u.raw_video.bytes_per_row;
#ifdef TRACE_AV_CODEC
char buffer[1024];
string_for_format(*inOutFormat, buffer, sizeof(buffer));
TRACE("[v] outFormat = %s\n", buffer);
TRACE(" returned video format 0x%x\n",
inOutFormat->u.raw_video.display.format);
#endif
return B_OK;
}
Besides the main duty described above, this method also fills out the other
output parameters as documented below.
\param outBuffer Pointer to the output buffer to copy the decoded audio
frames to.
\param outFrameCount Pointer to the output variable to assign the number of
copied audio frames (usually several audio frames at once).
\param mediaHeader Pointer to the output media header that contains the
properties of the decoded audio frame being the first in the outBuffer.
\param info Specifies additional decoding parameters. (Note: unused).
\returns B_OK Decoding audio frames succeeded.
\returns B_LAST_BUFFER_ERROR There are no more audio frames available.
\returns Other error codes
*/
status_t
AVCodecDecoder::_DecodeAudio(void* outBuffer, int64* outFrameCount,
media_header* mediaHeader, media_decode_info* info)
{
TRACE_AUDIO("AVCodecDecoder::_DecodeAudio(audio start_time %.6fs)\n",
mediaHeader->start_time / 1000000.0);
status_t audioDecodingStatus
= fDecodedDataSizeInBytes > 0 ? B_OK : _DecodeNextAudioFrame();
if (audioDecodingStatus != B_OK)
return audioDecodingStatus;
*outFrameCount = fDecodedDataSizeInBytes / fOutputFrameSize;
*mediaHeader = fHeader;
memcpy(outBuffer, fDecodedData, fDecodedDataSizeInBytes);
fDecodedDataSizeInBytes = 0;
return B_OK;
}
Besides the main duty described above, this method also fills out the other
output parameters as documented below.
\param outBuffer Pointer to the output buffer to copy the decoded video
frame to.
\param outFrameCount Pointer to the output variable to assign the number of
copied video frames (usually one video frame).
\param mediaHeader Pointer to the output media header that contains the
decoded video frame properties.
\param info Specifies additional decoding parameters. (Note: unused).
\returns B_OK Decoding a video frame succeeded.
\returns B_LAST_BUFFER_ERROR There are no more video frames available.
\returns Other error codes
*/
status_t
AVCodecDecoder::_DecodeVideo(void* outBuffer, int64* outFrameCount,
media_header* mediaHeader, media_decode_info* info)
{
status_t videoDecodingStatus
= fDecodedDataSizeInBytes > 0 ? B_OK : _DecodeNextVideoFrame();
if (videoDecodingStatus != B_OK)
return videoDecodingStatus;
*outFrameCount = 1;
*mediaHeader = fHeader;
memcpy(outBuffer, fDecodedData, mediaHeader->size_used);
fDecodedDataSizeInBytes = 0;
return B_OK;
}
We decode at least one audio frame into fDecodedData. To achieve this goal,
we might need to request several chunks of encoded data resulting in a
variable execution time of this function.
The length of the decoded audio frame(s) is stored in
fDecodedDataSizeInBytes. If this variable is greater than zero you can
assert that all audio frames in fDecodedData are valid.
It is assumed that the number of expected audio frames is stored in
fOutputFrameCount. So _DecodeNextAudioFrame() must be called only after
fOutputFrameCount has been set.
Note: fOutputFrameCount contains the maximum number of frames a caller
of BMediaDecoder::Decode() expects to receive. There is a direct
relationship between fOutputFrameCount and the buffer size a caller of
BMediaDecoder::Decode() will provide so we make sure to respect this limit
for fDecodedDataSizeInBytes.
On return with status code B_OK the following conditions hold true:
1. fDecodedData contains as much audio frames as the caller of
BMediaDecoder::Decode() expects.
2. fDecodedData contains lesser audio frames as the caller of
BMediaDecoder::Decode() expects only when one of the following
conditions hold true:
i No more audio frames left. Consecutive calls to
_DecodeNextAudioFrame() will then result in the return of
status code B_LAST_BUFFER_ERROR.
ii TODO: A change in the size of the audio frames.
3. fHeader is populated with the audio frame properties of the first
audio frame in fDecodedData. Especially the start_time field of
fHeader relates to that first audio frame. Start times of
consecutive audio frames in fDecodedData have to be calculated
manually (using the frame rate and the frame duration) if the
caller needs them.
TODO: Handle change of channel_count. Such a change results in a change of
the audio frame size and thus has different buffer requirements.
The most sane approach for implementing this is to return the audio frames
that were still decoded with the previous channel_count and inform the
client of BMediaDecoder::Decode() about the change so that it can adapt to
it. Furthermore we need to adapt our fDecodedData to the new buffer size
requirements accordingly.
\returns B_OK when we successfully decoded enough audio frames
\returns B_LAST_BUFFER_ERROR when there are no more audio frames available.
\returns Other Errors
*/
status_t
AVCodecDecoder::_DecodeNextAudioFrame()
{
assert(fTempPacket->size >= 0);
assert(fDecodedDataSizeInBytes == 0);
status_t resetStatus = _ResetRawDecodedAudio();
if (resetStatus != B_OK)
return resetStatus;
while (fRawDecodedAudio->nb_samples < fOutputFrameCount) {
_CheckAndFixConditionsThatHintAtBrokenAudioCodeBelow();
bool decodedDataBufferHasData = fDecodedDataBufferSize > 0;
if (decodedDataBufferHasData) {
_MoveAudioFramesToRawDecodedAudioAndUpdateStartTimes();
continue;
}
status_t decodeAudioChunkStatus = _DecodeNextAudioFrameChunk();
if (decodeAudioChunkStatus == B_LAST_BUFFER_ERROR
&& fRawDecodedAudio->nb_samples > 0)
break;
if (decodeAudioChunkStatus != B_OK)
return decodeAudioChunkStatus;
}
fFrame += fRawDecodedAudio->nb_samples;
fDecodedDataSizeInBytes = fRawDecodedAudio->linesize[0];
_UpdateMediaHeaderForAudioFrame();
#ifdef DEBUG
dump_ffframe_audio(fRawDecodedAudio, "ffaudi");
#endif
TRACE_AUDIO(" frame count: %d current: %" B_PRId64 "\n",
fRawDecodedAudio->nb_samples, fFrame);
return B_OK;
}
passed to AVCodecDecoder when Setup() was called.
Note: This function must be called before the AVCodec is opened via
avcodec_open2(). Otherwise the behaviour of FFMPEG's audio decoding
function avcodec_receive_frame() is undefined.
Essential properties applied from fInputFormat.u.encoded_audio:
- bit_rate copied to fCodecContext->bit_rate
- frame_size copied to fCodecContext->frame_size
- output.format converted to fCodecContext->sample_fmt
- output.frame_rate copied to fCodecContext->sample_rate
- output.channel_count copied to fCodecContext->channels
Other essential properties being applied:
- fBlockAlign to fCodecContext->block_align
- fExtraData to fCodecContext->extradata
- fExtraDataSize to fCodecContext->extradata_size
TODO: Either the following documentation section should be removed or this
TODO when it is clear whether fInputFormat.MetaData() and
fInputFormat.MetaDataSize() have to be applied to fCodecContext. See the related
TODO in the method implementation.
Only applied when fInputFormat.MetaDataSize() is greater than zero:
- fInputFormat.MetaData() to fCodecContext->extradata
- fInputFormat.MetaDataSize() to fCodecContext->extradata_size
*/
void
AVCodecDecoder::_ApplyEssentialAudioContainerPropertiesToContext()
{
media_encoded_audio_format containerProperties
= fInputFormat.u.encoded_audio;
fCodecContext->bit_rate
= static_cast<int>(containerProperties.bit_rate);
fCodecContext->frame_size
= static_cast<int>(containerProperties.frame_size);
ConvertRawAudioFormatToAVSampleFormat(
containerProperties.output.format, fCodecContext->sample_fmt);
ConvertRawAudioFormatToAVSampleFormat(
containerProperties.output.format, fCodecContext->request_sample_fmt);
fCodecContext->sample_rate
= static_cast<int>(containerProperties.output.frame_rate);
int channel_count = static_cast<int>(containerProperties.output.channel_count);
if (channel_count == 0) {
TRACE(" channel_count still a wild-card, assuming stereo.\n");
set_channel_count(fCodecContext, 2);
} else
set_channel_count(fCodecContext, channel_count);
fCodecContext->block_align = fBlockAlign;
fCodecContext->extradata = reinterpret_cast<uint8_t*>(fExtraData);
fCodecContext->extradata_size = fExtraDataSize;
if (fInputFormat.MetaDataSize() > 0) {
fCodecContext->extradata = static_cast<uint8_t*>(
const_cast<void*>(fInputFormat.MetaData()));
fCodecContext->extradata_size = fInputFormat.MetaDataSize();
}
TRACE(" bit_rate %" PRId64 ", sample_rate %d, channels %d, block_align %d, "
"extradata_size %d\n",
fCodecContext->bit_rate,
fCodecContext->sample_rate,
fCodecContext->ch_layout.nb_channels,
fCodecContext->block_align,
fCodecContext->extradata_size);
}
Note: Also initializes fDecodedData if not done already.
\returns B_OK Resetting successfully completed.
\returns B_NO_MEMORY No memory left for correct operation.
*/
status_t
AVCodecDecoder::_ResetRawDecodedAudio()
{
if (fDecodedData == NULL) {
size_t maximumSizeOfDecodedData = fOutputFrameCount * fOutputFrameSize;
fDecodedData
= static_cast<uint8_t*>(malloc(maximumSizeOfDecodedData));
}
if (fDecodedData == NULL)
return B_NO_MEMORY;
fRawDecodedAudio->data[0] = fDecodedData;
fRawDecodedAudio->linesize[0] = 0;
fRawDecodedAudio->format = AV_SAMPLE_FMT_NONE;
fRawDecodedAudio->pkt_dts = AV_NOPTS_VALUE;
fRawDecodedAudio->nb_samples = 0;
memset(fRawDecodedAudio->opaque, 0, sizeof(avformat_codec_context));
return B_OK;
}
reports them and assigns valid values.
Note: This method is intended to be called before any code is executed that
deals with moving, loading or decoding any audio frames.
*/
void
AVCodecDecoder::_CheckAndFixConditionsThatHintAtBrokenAudioCodeBelow()
{
if (fDecodedDataBufferSize < 0) {
fprintf(stderr, "Decoding read past the end of the decoded data "
"buffer! %" B_PRId32 "\n", fDecodedDataBufferSize);
fDecodedDataBufferSize = 0;
}
if (fTempPacket->size < 0) {
fprintf(stderr, "Decoding read past the end of the temp packet! %d\n",
fTempPacket->size);
fTempPacket->size = 0;
}
}
thus to fDecodedData) and updates the start times of fRawDecodedAudio,
fDecodedDataBuffer and fTempPacket accordingly.
When moving audio frames to fRawDecodedAudio this method also makes sure
that the following important fields of fRawDecodedAudio are populated and
updated with correct values:
- fRawDecodedAudio->data[0]: Points to first free byte of fDecodedData
- fRawDecodedAudio->linesize[0]: Total size of frames in fDecodedData
- fRawDecodedAudio->format: Format of first audio frame
- fRawDecodedAudio->pkt_dts: Start time of first audio frame
- fRawDecodedAudio->nb_samples: Number of audio frames
- fRawDecodedAudio->opaque: Contains the following fields for the first
audio frame:
- channels: Channel count of first audio frame
- sample_rate: Frame rate of first audio frame
This function assumes to be called only when the following assumptions
hold true:
1. There are decoded audio frames available in fDecodedDataBuffer
meaning that fDecodedDataBufferSize is greater than zero.
2. There is space left in fRawDecodedAudio to move some audio frames
in. This means that fRawDecodedAudio has lesser audio frames than
the maximum allowed (specified by fOutputFrameCount).
3. The audio frame rate is known so that we can calculate the time
range (covered by the moved audio frames) to update the start times
accordingly.
4. The field fRawDecodedAudio->opaque points to a memory block
representing a structure of type avformat_codec_context.
After this function returns the caller can safely make the following
assumptions:
1. The number of decoded audio frames in fDecodedDataBuffer is
decreased though it may still be greater then zero.
2. The number of frames in fRawDecodedAudio has increased and all
important fields are updated (see listing above).
3. Start times of fDecodedDataBuffer and fTempPacket were increased
with the time range covered by the moved audio frames.
Note: This function raises an exception (by calling the debugger), when
fDecodedDataBufferSize is not a multiple of fOutputFrameSize.
*/
void
AVCodecDecoder::_MoveAudioFramesToRawDecodedAudioAndUpdateStartTimes()
{
assert(fDecodedDataBufferSize > 0);
assert(fRawDecodedAudio->nb_samples < fOutputFrameCount);
assert(fOutputFrameRate > 0);
int32 outFrames = fOutputFrameCount - fRawDecodedAudio->nb_samples;
int32 inFrames = fDecodedDataBufferSize;
int32 frames = min_c(outFrames, inFrames);
if (frames == 0)
debugger("fDecodedDataBufferSize not multiple of frame size!");
if (av_sample_fmt_is_planar(fCodecContext->sample_fmt)) {
#if 0
const uint8_t* ptr[8];
for (int i = 0; i < 8; i++) {
if (fDecodedDataBuffer->data[i] == NULL)
ptr[i] = NULL;
else
ptr[i] = fDecodedDataBuffer->data[i] + fDecodedDataBufferOffset;
}
inFrames = frames;
frames = swr_convert(fResampleContext, fRawDecodedAudio->data,
outFrames, ptr, inFrames);
if (frames < 0)
debugger("resampling failed");
#else
uintptr_t out = (uintptr_t)fRawDecodedAudio->data[0];
int32 offset = fDecodedDataBufferOffset;
for (int i = 0; i < frames; i++) {
for (int j = 0; j < get_channel_count(fCodecContext); j++) {
memcpy((void*)out, fDecodedDataBuffer->data[j]
+ offset, fInputFrameSize);
out += fInputFrameSize;
}
offset += fInputFrameSize;
}
outFrames = frames;
inFrames = frames;
#endif
} else {
memcpy(fRawDecodedAudio->data[0], fDecodedDataBuffer->data[0]
+ fDecodedDataBufferOffset, frames * fOutputFrameSize);
outFrames = frames;
inFrames = frames;
}
size_t remainingSize = inFrames * fInputFrameSize;
size_t decodedSize = outFrames * fOutputFrameSize;
fDecodedDataBufferSize -= inFrames;
bool firstAudioFramesCopiedToRawDecodedAudio
= fRawDecodedAudio->data[0] != fDecodedData;
if (!firstAudioFramesCopiedToRawDecodedAudio) {
fRawDecodedAudio->format = fDecodedDataBuffer->format;
fRawDecodedAudio->pkt_dts = fDecodedDataBuffer->pkt_dts;
avformat_codec_context* codecContext
= static_cast<avformat_codec_context*>(fRawDecodedAudio->opaque);
codecContext->channels = get_channel_count(fCodecContext);
codecContext->sample_rate = fCodecContext->sample_rate;
}
fRawDecodedAudio->data[0] += decodedSize;
fRawDecodedAudio->linesize[0] += decodedSize;
fRawDecodedAudio->nb_samples += outFrames;
fDecodedDataBufferOffset += remainingSize;
bigtime_t framesTimeInterval = static_cast<bigtime_t>(
(1000000LL * frames) / fOutputFrameRate);
fDecodedDataBuffer->pkt_dts += framesTimeInterval;
fTempPacket->dts += framesTimeInterval;
}
This method handles all the details of loading the input buffer
(fChunkBuffer) at the right time and of calling FFMPEG often engouh until
some audio frames have been decoded.
FFMPEG decides how much audio frames belong to a chunk. Because of that
it is very likely that _DecodeNextAudioFrameChunk has to be called several
times to decode enough audio frames to please the caller of
BMediaDecoder::Decode().
This function assumes to be called only when the following assumptions
hold true:
1. fDecodedDataBufferSize equals zero.
After this function returns successfully the caller can safely make the
following assumptions:
1. fDecodedDataBufferSize is greater than zero.
2. fDecodedDataBufferOffset is set to zero.
3. fDecodedDataBuffer contains audio frames.
\returns B_OK on successfully decoding one audio frame chunk.
\returns B_LAST_BUFFER_ERROR No more audio frame chunks available. From
this point on further calls will return this same error.
\returns B_ERROR Decoding failed
*/
status_t
AVCodecDecoder::_DecodeNextAudioFrameChunk()
{
assert(fDecodedDataBufferSize == 0);
while (fDecodedDataBufferSize == 0) {
status_t loadingChunkStatus
= _LoadNextChunkIfNeededAndAssignStartTime();
if (loadingChunkStatus != B_OK)
return loadingChunkStatus;
status_t decodingStatus
= _DecodeSomeAudioFramesIntoEmptyDecodedDataBuffer();
if (decodingStatus != B_OK) {
memset(fDecodedData, 0, fRawDecodedAudio->linesize[0]);
if (!fAudioDecodeError) {
int32 chunkBufferOffset = fTempPacket->data - fChunkBuffer;
printf("########### audio decode error, "
"fTempPacket->size %d, fChunkBuffer data offset %" B_PRId32
"\n", fTempPacket->size, chunkBufferOffset);
fAudioDecodeError = true;
}
continue;
}
fAudioDecodeError = false;
}
return B_OK;
}
fDecodedDataBuffer.
This function assumes to be called only when the following assumptions
hold true:
1. fDecodedDataBufferSize equals zero.
2. fTempPacket->size is greater than zero.
After this function returns successfully the caller can safely make the
following assumptions:
1. fDecodedDataBufferSize is greater than zero in the common case.
Also see "Note" below.
2. fTempPacket was updated to exclude the data chunk that was consumed
by avcodec_send_packet().
3. fDecodedDataBufferOffset is set to zero.
When this function failed to decode at least one audio frame due to a
decoding error the caller can safely make the following assumptions:
1. fDecodedDataBufferSize equals zero.
2. fTempPacket->size equals zero.
Note: It is possible that there wasn't any audio frame decoded into
fDecodedDataBuffer after calling this function. This is normal and can
happen when there was either a decoding error or there is some decoding
delay in FFMPEGs audio decoder. Another call to this method is totally
safe and is even expected as long as the calling assumptions hold true.
\returns B_OK Decoding successful. fDecodedDataBuffer contains decoded
audio frames only when fDecodedDataBufferSize is greater than zero.
fDecodedDataBuffer is empty, when avcodec_receive_frame() didn't return
audio frames due to delayed decoding or incomplete audio frames.
\returns B_ERROR Decoding failed thus fDecodedDataBuffer contains no audio
frames.
*/
status_t
AVCodecDecoder::_DecodeSomeAudioFramesIntoEmptyDecodedDataBuffer()
{
assert(fDecodedDataBufferSize == 0);
av_frame_unref(fDecodedDataBuffer);
fDecodedDataBufferOffset = 0;
int error = avcodec_receive_frame(fCodecContext, fDecodedDataBuffer);
if (error == AVERROR_EOF)
return B_LAST_BUFFER_ERROR;
if (error == AVERROR(EAGAIN)) {
avcodec_send_packet(fCodecContext, fTempPacket);
fTempPacket->size = 0;
error = avcodec_receive_frame(fCodecContext, fDecodedDataBuffer);
}
fDecodedDataBufferSize = fDecodedDataBuffer->nb_samples;
if (fDecodedDataBufferSize < 0)
fDecodedDataBufferSize = 0;
if (error == 0)
return B_OK;
else
return B_ERROR;
}
properties of the most recently decoded audio frame.
The following fields of fHeader are updated:
- fHeader.type
- fHeader.file_pos
- fHeader.orig_size
- fHeader.start_time
- fHeader.size_used
- fHeader.u.raw_audio.frame_rate
- fHeader.u.raw_audio.channel_count
It is assumed that this function is called only when the following asserts
hold true:
1. We actually got a new audio frame decoded by the audio decoder.
2. fHeader wasn't updated for the new audio frame yet. You MUST call
this method only once per decoded audio frame.
3. fRawDecodedAudio's fields relate to the first audio frame contained
in fDecodedData. Especially the following fields are of importance:
- fRawDecodedAudio->pkt_dts: Start time of first audio frame
- fRawDecodedAudio->opaque: Contains the following fields for
the first audio frame:
- channels: Channel count of first audio frame
- sample_rate: Frame rate of first audio frame
*/
void
AVCodecDecoder::_UpdateMediaHeaderForAudioFrame()
{
fHeader.type = B_MEDIA_RAW_AUDIO;
fHeader.file_pos = 0;
fHeader.orig_size = 0;
fHeader.start_time = fRawDecodedAudio->pkt_dts;
fHeader.size_used = fRawDecodedAudio->linesize[0];
avformat_codec_context* codecContext
= static_cast<avformat_codec_context*>(fRawDecodedAudio->opaque);
fHeader.u.raw_audio.channel_count = codecContext->channels;
fHeader.u.raw_audio.frame_rate = codecContext->sample_rate;
}
We decode exactly one video frame into fDecodedData. To achieve this goal,
we might need to request several chunks of encoded data resulting in a
variable execution time of this function.
The length of the decoded video frame is stored in
fDecodedDataSizeInBytes. If this variable is greater than zero, you can
assert that there is a valid video frame available in fDecodedData.
The decoded video frame in fDecodedData has color space conversion and
deinterlacing already applied.
To every decoded video frame there is a media_header populated in
fHeader, containing the corresponding video frame properties.
Normally every decoded video frame has a start_time field populated in the
associated fHeader, that determines the presentation time of the frame.
This relationship will only hold true, when each data chunk that is
provided via GetNextChunk() contains data for exactly one encoded video
frame (one complete frame) - not more and not less.
We can decode data chunks that contain partial video frame data, too. In
that case, you cannot trust the value of the start_time field in fHeader.
We simply have no logic in place to establish a meaningful relationship
between an incomplete frame and the start time it should be presented.
Though this might change in the future.
We can decode data chunks that contain more than one video frame, too. In
that case, you cannot trust the value of the start_time field in fHeader.
We simply have no logic in place to track the start_time across multiple
video frames. So a meaningful relationship between the 2nd, 3rd, ... frame
and the start time it should be presented isn't established at the moment.
Though this might change in the future.
On first call the member variables fSwsContext / fFormatConversionFunc are
initialized.
\returns B_OK when we successfully decoded one video frame
\returns B_LAST_BUFFER_ERROR when there are no more video frames available.
\returns B_NO_MEMORY when we have no memory left for correct operation.
\returns Other Errors
*/
status_t
AVCodecDecoder::_DecodeNextVideoFrame()
{
int error;
int send_error;
#if DO_PROFILING
bigtime_t startTime = system_time();
#endif
error = avcodec_receive_frame(fCodecContext, fRawDecodedPicture);
if (error == AVERROR_EOF)
return B_LAST_BUFFER_ERROR;
if (error == AVERROR(EAGAIN)) {
do {
status_t loadingChunkStatus
= _LoadNextChunkIfNeededAndAssignStartTime();
if (loadingChunkStatus == B_LAST_BUFFER_ERROR)
return _FlushOneVideoFrameFromDecoderBuffer();
if (loadingChunkStatus != B_OK) {
TRACE("[v] AVCodecDecoder::_DecodeNextVideoFrame(): error from "
"GetNextChunk(): %s\n", strerror(loadingChunkStatus));
return loadingChunkStatus;
}
char timestamp[AV_TS_MAX_STRING_SIZE];
av_ts_make_time_string(timestamp,
fTempPacket->dts, &fCodecContext->time_base);
TRACE("[v] Feed %d more bytes (dts %s)\n", fTempPacket->size,
timestamp);
send_error = avcodec_send_packet(fCodecContext, fTempPacket);
if (send_error < 0 && send_error != AVERROR(EAGAIN)) {
TRACE("[v] AVCodecDecoder: ignoring error in decoding frame %" B_PRId64 ": %d\n",
fFrame, error);
}
fTempPacket->data = NULL;
fTempPacket->size = 0;
error = avcodec_receive_frame(fCodecContext, fRawDecodedPicture);
if (error != 0 && error != AVERROR(EAGAIN)) {
TRACE("[v] frame %" B_PRId64 " decoding error: error code: %d, chunk size: %ld\n",
fFrame, error, fChunkBufferSize);
}
} while (error != 0);
}
#if DO_PROFILING
bigtime_t formatConversionStart = system_time();
#endif
status_t handleStatus = _HandleNewVideoFrameAndUpdateSystemState();
if (handleStatus != B_OK)
return handleStatus;
#if DO_PROFILING
bigtime_t doneTime = system_time();
decodingTime += formatConversionStart - startTime;
conversionTime += doneTime - formatConversionStart;
profileCounter++;
if (!(fFrame % 5)) {
printf("[v] profile: d1 = %lld, d2 = %lld (%lld)\n",
decodingTime / profileCounter, conversionTime / profileCounter,
fFrame);
decodingTime = 0;
conversionTime = 0;
profileCounter = 0;
}
#endif
return error;
}
passed to AVCodecDecoder when Setup() was called.
Note: This function must be called before the AVCodec is opened via
avcodec_open2(). Otherwise the behaviour of FFMPEG's video decoding
function avcodec_decode_video2() is undefined.
Essential properties applied from fInputFormat.u.encoded_video.output:
- display.line_width copied to fCodecContext->width
- display.line_count copied to fCodecContext->height
- pixel_width_aspect and pixel_height_aspect converted to
fCodecContext->sample_aspect_ratio
- field_rate converted to fCodecContext->time_base and
fCodecContext->ticks_per_frame
Other essential properties being applied:
- fExtraData to fCodecContext->extradata
- fExtraDataSize to fCodecContext->extradata_size
*/
void
AVCodecDecoder::_ApplyEssentialVideoContainerPropertiesToContext()
{
media_raw_video_format containerProperties
= fInputFormat.u.encoded_video.output;
fCodecContext->width = containerProperties.display.line_width;
fCodecContext->height = containerProperties.display.line_count;
if (containerProperties.pixel_width_aspect > 0
&& containerProperties.pixel_height_aspect > 0) {
ConvertVideoAspectWidthAndHeightToAVCodecContext(
containerProperties.pixel_width_aspect,
containerProperties.pixel_height_aspect, *fCodecContext);
}
if (containerProperties.field_rate > 0.0) {
ConvertVideoFrameRateToAVCodecContext(containerProperties.field_rate,
*fCodecContext);
}
fCodecContext->extradata = reinterpret_cast<uint8_t*>(fExtraData);
fCodecContext->extradata_size = fExtraDataSize;
}
the start time) to fTempPacket but only if fTempPacket is empty.
\returns B_OK
1. meaning: Next chunk is loaded.
2. meaning: No need to load and assign anything. Proceed as usual.
\returns B_LAST_BUFFER_ERROR No more chunks available. fChunkBuffer and
fTempPacket are left untouched.
\returns Other errors Caller should bail out because fChunkBuffer and
fTempPacket are in unknown states. Normal operation cannot be
guaranteed.
*/
status_t
AVCodecDecoder::_LoadNextChunkIfNeededAndAssignStartTime()
{
if (fTempPacket->size > 0)
return B_OK;
const void* chunkBuffer = NULL;
size_t chunkBufferSize = 0;
media_header chunkMediaHeader;
status_t getNextChunkStatus = GetNextChunk(&chunkBuffer, &chunkBufferSize,
&chunkMediaHeader);
if (getNextChunkStatus != B_OK)
return getNextChunkStatus;
status_t chunkBufferPaddingStatus
= _CopyChunkToChunkBufferAndAddPadding(chunkBuffer, chunkBufferSize);
if (chunkBufferPaddingStatus != B_OK)
return chunkBufferPaddingStatus;
fTempPacket->data = fChunkBuffer;
fTempPacket->size = fChunkBufferSize;
fTempPacket->dts = chunkMediaHeader.start_time;
if (chunkMediaHeader.user_data_type == AVPACKET_USER_DATA_TYPE) {
avpacket_user_data* data = (avpacket_user_data*)&chunkMediaHeader.user_data;
fTempPacket->pts = data->pts;
fTempPacket->dts = data->dts;
fTempPacket->stream_index = data->stream_index;
fTempPacket->flags = data->flags;
fTempPacket->duration = data->duration;
fTempPacket->pos = data->pos;
}
#ifdef LOG_STREAM_TO_FILE
BFile* logFile = fIsAudio ? &sAudioStreamLogFile : &sVideoStreamLogFile;
if (sDumpedPackets < 100) {
logFile->Write(chunkBuffer, fChunkBufferSize);
printf("wrote %ld bytes\n", fChunkBufferSize);
sDumpedPackets++;
} else if (sDumpedPackets == 100)
logFile->Unset();
#endif
return B_OK;
}
additional memory as required by FFMPEG for input buffers to video
decoders.
This is needed so that some decoders can read safely a predefined number of
bytes at a time for performance optimization purposes.
The additional memory has a size of AV_INPUT_BUFFER_PADDING_SIZE as defined
in avcodec.h.
Ownership of fChunkBuffer memory is with the class so it needs to be freed
at the right times (on destruction, on seeking).
Also update fChunkBufferSize to reflect the size of the contained data
(leaving out the padding).
\param chunk The chunk to copy.
\param chunkSize Size of the chunk in bytes
\returns B_OK Padding was successful. You are responsible for releasing the
allocated memory. fChunkBufferSize is set to chunkSize.
\returns B_NO_MEMORY Padding failed.
fChunkBuffer is set to NULL making it safe to call free() on it.
fChunkBufferSize is set to 0 to reflect the size of fChunkBuffer.
*/
status_t
AVCodecDecoder::_CopyChunkToChunkBufferAndAddPadding(const void* chunk,
size_t chunkSize)
{
uint8_t* tmpBuffer = static_cast<uint8_t*>(realloc(fChunkBuffer,
chunkSize + AV_INPUT_BUFFER_PADDING_SIZE));
if (tmpBuffer == NULL) {
free(fChunkBuffer);
fChunkBuffer = NULL;
fChunkBufferSize = 0;
return B_NO_MEMORY;
} else {
fChunkBuffer = tmpBuffer;
}
memcpy(fChunkBuffer, chunk, chunkSize);
memset(fChunkBuffer + chunkSize, 0, AV_INPUT_BUFFER_PADDING_SIZE);
fChunkBufferSize = chunkSize;
return B_OK;
}
\see _UpdateMediaHeaderForVideoFrame() and
\see _DeinterlaceAndColorConvertVideoFrame() for when you are allowed to
call this method.
\returns B_OK when video frame was handled successfully
\returnb B_NO_MEMORY when no memory is left for correct operation.
*/
status_t
AVCodecDecoder::_HandleNewVideoFrameAndUpdateSystemState()
{
_UpdateMediaHeaderForVideoFrame();
status_t postProcessStatus = _DeinterlaceAndColorConvertVideoFrame();
if (postProcessStatus != B_OK)
return postProcessStatus;
#ifdef DEBUG
dump_ffframe_video(fRawDecodedPicture, "ffpict");
#endif
fFrame++;
return B_OK;
}
Some FFMPEG decoder are buffering video frames. To retrieve those buffered
frames the decoder needs to be told so.
The intended use of this method is to call it, once there are no more data
chunks for decoding left. Reframed in other words: Once GetNextChunk()
returns with status B_LAST_BUFFER_ERROR it is time to start flushing.
\returns B_OK Retrieved one video frame, handled it accordingly and updated
the system state accordingly.
There maybe more video frames left. So it is valid for the client of
AVCodecDecoder to call it one more time.
\returns B_LAST_BUFFER_ERROR No video frame left.
The client of the AVCodecDecoder should stop calling it now.
\returns B_NO_MEMORY No memory left for correct operation.
*/
status_t
AVCodecDecoder::_FlushOneVideoFrameFromDecoderBuffer()
{
avcodec_send_packet(fCodecContext, NULL);
int error = avcodec_receive_frame(fCodecContext, fRawDecodedPicture);
if (error != 0 && error != AVERROR(EAGAIN)) {
return B_LAST_BUFFER_ERROR;
}
return _HandleNewVideoFrameAndUpdateSystemState();
}
properties of the most recently decoded video frame.
It is assumed that this function is called only when the following asserts
hold true:
1. We actually got a new picture decoded by the video decoder.
2. fHeader wasn't updated for the new picture yet. You MUST call this
method only once per decoded video frame.
3. This function MUST be called after
_DeinterlaceAndColorConvertVideoFrame() as it relys on an updated
fDecodedDataSizeInBytes.
4. There will be at maximumn only one decoded video frame in our cache
at any single point in time. Otherwise you couldn't tell to which
cached decoded video frame the properties in fHeader relate to.
5. AVCodecContext is still valid for this video frame (This is the case
when this function is called after avcodec_decode_video2() and
before the next call to avcodec_decode_video2().
*/
void
AVCodecDecoder::_UpdateMediaHeaderForVideoFrame()
{
fHeader.type = B_MEDIA_RAW_VIDEO;
fHeader.file_pos = 0;
fHeader.orig_size = 0;
fHeader.start_time = fRawDecodedPicture->best_effort_timestamp;
fHeader.size_used = av_image_get_buffer_size(
colorspace_to_pixfmt(fOutputColorSpace), fRawDecodedPicture->width,
fRawDecodedPicture->height, 1);
fHeader.u.raw_video.display_line_width = fRawDecodedPicture->width;
fHeader.u.raw_video.display_line_count = fRawDecodedPicture->height;
fHeader.u.raw_video.bytes_per_row
= CalculateBytesPerRowWithColorSpaceAndVideoWidth(fOutputColorSpace,
fRawDecodedPicture->width);
fHeader.u.raw_video.field_gamma = 1.0;
fHeader.u.raw_video.field_sequence = fFrame;
fHeader.u.raw_video.field_number = 0;
fHeader.u.raw_video.pulldown_number = 0;
fHeader.u.raw_video.first_active_line = 1;
fHeader.u.raw_video.line_count = fRawDecodedPicture->height;
ConvertAVCodecContextToVideoAspectWidthAndHeight(*fCodecContext,
fHeader.u.raw_video.pixel_width_aspect,
fHeader.u.raw_video.pixel_height_aspect);
char timestamp[AV_TS_MAX_STRING_SIZE];
av_ts_make_time_string(timestamp,
fRawDecodedPicture->best_effort_timestamp, &fCodecContext->time_base);
TRACE("[v] start_time=%s field_sequence=%" B_PRIu32 "\n",
timestamp, fHeader.u.raw_video.field_sequence);
}
conversion to the video frame in fRawDecodedPicture.
It is assumed that fRawDecodedPicture wasn't deinterlaced and color
converted yet (otherwise this function behaves in unknown manners).
This function MUST be called after _UpdateMediaHeaderForVideoFrame() as it
relys on the fHeader.size_used and fHeader.u.raw_video.bytes_per_row fields
for correct operation
You should only call this function when you got a new picture decoded by
the video decoder.
When this function finishes the postprocessed video frame will be available
in fPostProcessedDecodedPicture and fDecodedData (fDecodedDataSizeInBytes
will be set accordingly).
\returns B_OK video frame successfully deinterlaced and color converted.
\returns B_NO_MEMORY Not enough memory available for correct operation.
*/
status_t
AVCodecDecoder::_DeinterlaceAndColorConvertVideoFrame()
{
int displayWidth = fRawDecodedPicture->width;
int displayHeight = fRawDecodedPicture->height;
AVFrame deinterlacedPicture;
bool useDeinterlacedPicture = false;
if (fRawDecodedPicture->flags & AV_FRAME_FLAG_INTERLACED) {
AVFrame rawPicture;
rawPicture.data[0] = fRawDecodedPicture->data[0];
rawPicture.data[1] = fRawDecodedPicture->data[1];
rawPicture.data[2] = fRawDecodedPicture->data[2];
rawPicture.data[3] = fRawDecodedPicture->data[3];
rawPicture.linesize[0] = fRawDecodedPicture->linesize[0];
rawPicture.linesize[1] = fRawDecodedPicture->linesize[1];
rawPicture.linesize[2] = fRawDecodedPicture->linesize[2];
rawPicture.linesize[3] = fRawDecodedPicture->linesize[3];
if (av_image_alloc(deinterlacedPicture.data,
deinterlacedPicture.linesize, displayWidth, displayHeight,
fCodecContext->pix_fmt, 1) < 0)
return B_NO_MEMORY;
_ProcessFilterGraph(&deinterlacedPicture, &rawPicture,
fCodecContext->pix_fmt, displayWidth, displayHeight);
useDeinterlacedPicture = true;
}
#if USE_SWS_FOR_COLOR_SPACE_CONVERSION
if (fSwsContext == NULL) {
fSwsContext = sws_getContext(displayWidth, displayHeight,
fCodecContext->pix_fmt, displayWidth, displayHeight,
colorspace_to_pixfmt(fOutputColorSpace),
SWS_FAST_BILINEAR, NULL, NULL, NULL);
}
#else
if (fFormatConversionFunc == NULL) {
fFormatConversionFunc = resolve_colorspace(fOutputColorSpace,
fCodecContext->pix_fmt, displayWidth, displayHeight);
}
#endif
fDecodedDataSizeInBytes = fHeader.size_used;
if (fDecodedData == NULL) {
const size_t kOptimalAlignmentForColorConversion = 32;
posix_memalign(reinterpret_cast<void**>(&fDecodedData),
kOptimalAlignmentForColorConversion, fDecodedDataSizeInBytes);
}
if (fDecodedData == NULL)
return B_NO_MEMORY;
fPostProcessedDecodedPicture->data[0] = fDecodedData;
fPostProcessedDecodedPicture->linesize[0]
= fHeader.u.raw_video.bytes_per_row;
#if USE_SWS_FOR_COLOR_SPACE_CONVERSION
if (fSwsContext != NULL) {
#else
if (fFormatConversionFunc != NULL) {
#endif
if (useDeinterlacedPicture) {
AVFrame deinterlacedFrame;
deinterlacedFrame.data[0] = deinterlacedPicture.data[0];
deinterlacedFrame.data[1] = deinterlacedPicture.data[1];
deinterlacedFrame.data[2] = deinterlacedPicture.data[2];
deinterlacedFrame.data[3] = deinterlacedPicture.data[3];
deinterlacedFrame.linesize[0]
= deinterlacedPicture.linesize[0];
deinterlacedFrame.linesize[1]
= deinterlacedPicture.linesize[1];
deinterlacedFrame.linesize[2]
= deinterlacedPicture.linesize[2];
deinterlacedFrame.linesize[3]
= deinterlacedPicture.linesize[3];
#if USE_SWS_FOR_COLOR_SPACE_CONVERSION
sws_scale(fSwsContext, deinterlacedFrame.data,
deinterlacedFrame.linesize, 0, displayHeight,
fPostProcessedDecodedPicture->data,
fPostProcessedDecodedPicture->linesize);
#else
(*fFormatConversionFunc)(&deinterlacedFrame,
fPostProcessedDecodedPicture, displayWidth, displayHeight);
#endif
} else {
#if USE_SWS_FOR_COLOR_SPACE_CONVERSION
sws_scale(fSwsContext, fRawDecodedPicture->data,
fRawDecodedPicture->linesize, 0, displayHeight,
fPostProcessedDecodedPicture->data,
fPostProcessedDecodedPicture->linesize);
#else
(*fFormatConversionFunc)(fRawDecodedPicture,
fPostProcessedDecodedPicture, displayWidth, displayHeight);
#endif
}
}
if (fRawDecodedPicture->flags & AV_FRAME_FLAG_INTERLACED)
av_freep(&deinterlacedPicture.data[0]);
return B_OK;
}
\returns B_OK the filter graph could be built.
\returns B_BAD_VALUE something was wrong with building the graph.
*/
status_t
AVCodecDecoder::_InitFilterGraph(enum AVPixelFormat pixfmt, int32 width,
int32 height)
{
if (fFilterGraph != NULL) {
av_frame_free(&fFilterFrame);
avfilter_graph_free(&fFilterGraph);
}
fFilterGraph = avfilter_graph_alloc();
BString arguments;
arguments.SetToFormat("buffer=video_size=%" B_PRId32 "x%" B_PRId32
":pix_fmt=%d:time_base=1/1:pixel_aspect=0/1[in];[in]yadif[out];"
"[out]buffersink", width, height,
pixfmt);
AVFilterInOut* inputs = NULL;
AVFilterInOut* outputs = NULL;
TRACE("[v] _InitFilterGraph(): %s\n", arguments.String());
int ret = avfilter_graph_parse2(fFilterGraph, arguments.String(), &inputs,
&outputs);
if (ret < 0) {
fprintf(stderr, "avfilter_graph_parse2() failed\n");
return B_BAD_VALUE;
}
ret = avfilter_graph_config(fFilterGraph, NULL);
if (ret < 0) {
fprintf(stderr, "avfilter_graph_config() failed\n");
return B_BAD_VALUE;
}
fBufferSourceContext = avfilter_graph_get_filter(fFilterGraph,
"Parsed_buffer_0");
fBufferSinkContext = avfilter_graph_get_filter(fFilterGraph,
"Parsed_buffersink_2");
if (fBufferSourceContext == NULL || fBufferSinkContext == NULL) {
fprintf(stderr, "avfilter_graph_get_filter() failed\n");
return B_BAD_VALUE;
}
fFilterFrame = av_frame_alloc();
fLastWidth = width;
fLastHeight = height;
fLastPixfmt = pixfmt;
return B_OK;
}
We decode exactly one video frame into dst.
Equivalent function for avpicture_deinterlace() from version 2.x.
\returns B_OK video frame successfully deinterlaced.
\returns B_BAD_DATA No frame could be output.
\returns B_NO_MEMORY Not enough memory available for correct operation.
*/
status_t
AVCodecDecoder::_ProcessFilterGraph(AVFrame *dst, const AVFrame *src,
enum AVPixelFormat pixfmt, int32 width, int32 height)
{
if (fFilterGraph == NULL || width != fLastWidth
|| height != fLastHeight || pixfmt != fLastPixfmt) {
status_t err = _InitFilterGraph(pixfmt, width, height);
if (err != B_OK)
return err;
}
memcpy(fFilterFrame->data, src->data, sizeof(src->data));
memcpy(fFilterFrame->linesize, src->linesize, sizeof(src->linesize));
fFilterFrame->width = width;
fFilterFrame->height = height;
fFilterFrame->format = pixfmt;
int ret = av_buffersrc_add_frame(fBufferSourceContext, fFilterFrame);
if (ret < 0)
return B_NO_MEMORY;
ret = av_buffersink_get_frame(fBufferSinkContext, fFilterFrame);
if (ret < 0)
return B_BAD_DATA;
av_image_copy(dst->data, dst->linesize, (const uint8**)fFilterFrame->data,
fFilterFrame->linesize, pixfmt, width, height);
av_frame_unref(fFilterFrame);
return B_OK;
}