* Copyright 2009-2010, Stephan Amßus <superstippi@gmx.de>
* Copyright 2018, Dario Casalinuovo
* All rights reserved. Distributed under the terms of the MIT license.
*/
#include "AVCodecEncoder.h"
#include <new>
#include <stdio.h>
#include <string.h>
#include <Application.h>
#include <Roster.h>
extern "C" {
#include "rational.h"
}
#include "EncoderTable.h"
#include "gfx_util.h"
#undef TRACE
#ifdef TRACE_AV_CODEC_ENCODER
# define TRACE printf
# define TRACE_IO(a...)
#else
# define TRACE(a...)
# define TRACE_IO(a...)
#endif
static const size_t kDefaultChunkBufferSize = 2 * 1024 * 1024;
AVCodecEncoder::AVCodecEncoder(uint32 codecID, int bitRateScale)
:
Encoder(),
fBitRateScale(bitRateScale),
fCodecID((CodecID)codecID),
fCodec(NULL),
fCodecContext(NULL),
fCodecInitStatus(CODEC_INIT_NEEDED),
fFrame(av_frame_alloc()),
fSwsContext(NULL),
fFramesWritten(0)
{
TRACE("AVCodecEncoder::AVCodecEncoder()\n");
_Init();
}
void
AVCodecEncoder::_Init()
{
fChunkBuffer = new(std::nothrow) uint8[kDefaultChunkBufferSize];
if (fCodecID > 0) {
fCodec = avcodec_find_encoder(fCodecID);
TRACE(" found AVCodec for %u: %p\n", fCodecID, fCodec);
}
#if LIBAVCODEC_VERSION_MAJOR >= 60
fAudioFifo = av_fifo_alloc2(0, 1, AV_FIFO_FLAG_AUTO_GROW);
#else
fAudioFifo = av_fifo_alloc(0);
#endif
fEncodeParameters.avg_field_size = 0;
fEncodeParameters.max_field_size = 0;
fEncodeParameters.quality = 1.0f;
}
AVCodecEncoder::~AVCodecEncoder()
{
TRACE("AVCodecEncoder::~AVCodecEncoder()\n");
if (fSwsContext != NULL)
sws_freeContext(fSwsContext);
#if LIBAVCODEC_VERSION_MAJOR >= 60
av_fifo_freep2(&fAudioFifo);
#else
av_fifo_free(fAudioFifo);
#endif
if (fFrame != NULL) {
av_frame_free(&fFrame);
}
if (fCodecContext != NULL) {
avcodec_close(fCodecContext);
avcodec_free_context(&fCodecContext);
}
delete[] fChunkBuffer;
}
status_t
AVCodecEncoder::AcceptedFormat(const media_format* proposedInputFormat,
media_format* _acceptedInputFormat)
{
TRACE("AVCodecEncoder::AcceptedFormat(%p, %p)\n", proposedInputFormat,
_acceptedInputFormat);
if (proposedInputFormat == NULL)
return B_BAD_VALUE;
if (_acceptedInputFormat != NULL) {
*_acceptedInputFormat = *proposedInputFormat;
}
return B_OK;
}
status_t
AVCodecEncoder::SetUp(const media_format* inputFormat)
{
TRACE("AVCodecEncoder::SetUp()\n");
if (inputFormat == NULL)
return B_BAD_VALUE;
if (fCodec == NULL && fCodecID == AV_CODEC_ID_NONE) {
fCodecID = raw_audio_codec_id_for(*inputFormat);
if (fCodecID != AV_CODEC_ID_NONE)
fCodec = avcodec_find_encoder(fCodecID);
}
if (fCodec == NULL) {
TRACE(" encoder not found!\n");
return B_NO_INIT;
}
fInputFormat = *inputFormat;
fFramesWritten = 0;
return _Setup();
}
status_t
AVCodecEncoder::GetEncodeParameters(encode_parameters* parameters) const
{
TRACE("AVCodecEncoder::GetEncodeParameters(%p)\n", parameters);
parameters->quality = fEncodeParameters.quality;
return B_OK;
}
status_t
AVCodecEncoder::SetEncodeParameters(encode_parameters* parameters)
{
TRACE("AVCodecEncoder::SetEncodeParameters(%p)\n", parameters);
if (fFramesWritten > 0)
return B_NOT_SUPPORTED;
fEncodeParameters.quality = parameters->quality;
TRACE(" quality: %.5f\n", parameters->quality);
if (fEncodeParameters.quality == 0.0f) {
TRACE(" using default quality (1.0)\n");
fEncodeParameters.quality = 1.0f;
}
return _Setup();
}
status_t
AVCodecEncoder::Encode(const void* buffer, int64 frameCount,
media_encode_info* info)
{
TRACE("AVCodecEncoder::Encode(%p, %" B_PRId64 ", %p)\n", buffer, frameCount, info);
if (!_OpenCodecIfNeeded())
return B_NO_INIT;
if (fInputFormat.type == B_MEDIA_RAW_AUDIO)
return _EncodeAudio(buffer, frameCount, info);
else if (fInputFormat.type == B_MEDIA_RAW_VIDEO)
return _EncodeVideo(buffer, frameCount, info);
else
return B_NO_INIT;
}
static int
get_channel_count(AVCodecContext* context)
{
#if LIBAVCODEC_VERSION_MAJOR >= 60
return context->ch_layout.nb_channels;
#else
return context->channels;
#endif
}
static void
set_channel_count(AVCodecContext* context, int count)
{
#if LIBAVCODEC_VERSION_MAJOR >= 60
context->ch_layout.nb_channels = count;
#else
context->channels = count;
#endif
}
status_t
AVCodecEncoder::_Setup()
{
TRACE("AVCodecEncoder::_Setup\n");
int rawBitRate;
if (fCodecContext != NULL) {
avcodec_close(fCodecContext);
avcodec_free_context(&fCodecContext);
}
fCodecContext = avcodec_alloc_context3(fCodec);
if (fCodecContext == NULL)
return B_NO_INIT;
if (fInputFormat.type == B_MEDIA_RAW_VIDEO) {
TRACE(" B_MEDIA_RAW_VIDEO\n");
AVPixelFormat pixFmt = colorspace_to_pixfmt(
fInputFormat.u.raw_video.display.format);
if (pixFmt == AV_PIX_FMT_NONE) {
TRACE("Invalid input colorspace\n");
return B_BAD_DATA;
}
fCodecContext->time_base = (AVRational){1, (int)fInputFormat.u.raw_video.field_rate};
fCodecContext->framerate = (AVRational){(int)fInputFormat.u.raw_video.field_rate, 1};
fCodecContext->width = fInputFormat.u.raw_video.display.line_width;
fCodecContext->height = fInputFormat.u.raw_video.display.line_count;
fCodecContext->gop_size = 12;
if (fCodec->pix_fmts != NULL) {
for (int i = 0; fCodec->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
fCodecContext->pix_fmt = fCodec->pix_fmts[i];
}
}
rawBitRate = (int)(fCodecContext->width * fCodecContext->height * 2
* fInputFormat.u.raw_video.field_rate) * 8;
fCodecContext->sample_aspect_ratio.num
= fInputFormat.u.raw_video.pixel_width_aspect;
fCodecContext->sample_aspect_ratio.den
= fInputFormat.u.raw_video.pixel_height_aspect;
if (fCodecContext->sample_aspect_ratio.num == 0
|| fCodecContext->sample_aspect_ratio.den == 0) {
av_reduce(&fCodecContext->sample_aspect_ratio.num,
&fCodecContext->sample_aspect_ratio.den, fCodecContext->width,
fCodecContext->height, 255);
}
if (fInputFormat.u.raw_video.display.bytes_per_row == 0) {
fInputFormat.u.raw_video.display.bytes_per_row
= fCodecContext->width * 4;
}
fFrame->pts = 0;
fSwsContext = sws_getContext(fCodecContext->width,
fCodecContext->height, pixFmt,
fCodecContext->width, fCodecContext->height,
fCodecContext->pix_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL);
} else if (fInputFormat.type == B_MEDIA_RAW_AUDIO) {
TRACE(" B_MEDIA_RAW_AUDIO\n");
fCodecContext->sample_rate = (int)fInputFormat.u.raw_audio.frame_rate;
set_channel_count(fCodecContext, fInputFormat.u.raw_audio.channel_count);
rawBitRate = fCodecContext->sample_rate * get_channel_count(fCodecContext)
* (fInputFormat.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK) * 8;
switch (fInputFormat.u.raw_audio.format) {
case media_raw_audio_format::B_AUDIO_FLOAT:
fCodecContext->sample_fmt = AV_SAMPLE_FMT_FLT;
break;
case media_raw_audio_format::B_AUDIO_DOUBLE:
fCodecContext->sample_fmt = AV_SAMPLE_FMT_DBL;
break;
case media_raw_audio_format::B_AUDIO_INT:
fCodecContext->sample_fmt = AV_SAMPLE_FMT_S32;
break;
case media_raw_audio_format::B_AUDIO_SHORT:
fCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;
break;
case media_raw_audio_format::B_AUDIO_UCHAR:
fCodecContext->sample_fmt = AV_SAMPLE_FMT_U8;
break;
case media_raw_audio_format::B_AUDIO_CHAR:
default:
return B_MEDIA_BAD_FORMAT;
break;
}
#if LIBAVCODEC_VERSION_MAJOR >= 60
if (fInputFormat.u.raw_audio.channel_mask == 0) {
av_channel_layout_default(&fCodecContext->ch_layout,
fInputFormat.u.raw_audio.channel_count);
} else {
av_channel_layout_from_mask(&fCodecContext->ch_layout,
fInputFormat.u.raw_audio.channel_mask);
}
#else
if (fInputFormat.u.raw_audio.channel_mask == 0) {
switch (fInputFormat.u.raw_audio.channel_count) {
default:
case 2:
fCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
break;
case 1:
fCodecContext->channel_layout = AV_CH_LAYOUT_MONO;
break;
case 3:
fCodecContext->channel_layout = AV_CH_LAYOUT_SURROUND;
break;
case 4:
fCodecContext->channel_layout = AV_CH_LAYOUT_QUAD;
break;
case 5:
fCodecContext->channel_layout = AV_CH_LAYOUT_5POINT0;
break;
case 6:
fCodecContext->channel_layout = AV_CH_LAYOUT_5POINT1;
break;
case 8:
fCodecContext->channel_layout = AV_CH_LAYOUT_7POINT1;
break;
case 10:
fCodecContext->channel_layout = AV_CH_LAYOUT_7POINT1_WIDE;
break;
}
} else {
fCodecContext->channel_layout = fInputFormat.u.raw_audio.channel_mask;
}
#endif
} else {
TRACE(" UNSUPPORTED MEDIA TYPE!\n");
return B_NOT_SUPPORTED;
}
int wantedBitRate = (int)(rawBitRate / fBitRateScale * fEncodeParameters.quality);
if (wantedBitRate == 0)
wantedBitRate = (int)(rawBitRate / fBitRateScale);
fCodecContext->bit_rate = wantedBitRate;
if (fInputFormat.type == B_MEDIA_RAW_AUDIO) {
const int kBitRates[] = {
32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000,
160000, 192000, 224000, 256000, 320000, 384000, 448000, 512000,
576000, 640000
};
int diff = wantedBitRate;
for (unsigned int i = 0; i < sizeof(kBitRates) / sizeof(int); i++) {
int currentDiff = abs(wantedBitRate - kBitRates[i]);
if (currentDiff < diff) {
fCodecContext->bit_rate = kBitRates[i];
diff = currentDiff;
} else
break;
}
}
TRACE(" rawBitRate: %d, wantedBitRate: %d (%.1f), context bitrate: %" PRId64 "\n",
rawBitRate, wantedBitRate, fEncodeParameters.quality, fCodecContext->bit_rate);
if (fCodecContext->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
fCodecContext->max_b_frames = 2;
} else if (fCodecContext->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
fCodecContext->mb_decision = 2;
}
return B_OK;
}
bool
AVCodecEncoder::_OpenCodecIfNeeded()
{
if (fCodecInitStatus == CODEC_INIT_DONE)
return true;
if (fCodecInitStatus == CODEC_INIT_FAILED)
return false;
fCodecContext->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
fFrame->format = fCodecContext->pix_fmt;
fFrame->width = fCodecContext->width;
fFrame->height = fCodecContext->height;
av_frame_get_buffer(fFrame, 0);
int result = avcodec_open2(fCodecContext, fCodec, NULL);
if (result >= 0)
fCodecInitStatus = CODEC_INIT_DONE;
else
fCodecInitStatus = CODEC_INIT_FAILED;
TRACE(" avcodec_open(%p, %p): %d\n", fCodecContext, fCodec, result);
return fCodecInitStatus == CODEC_INIT_DONE;
}
status_t
AVCodecEncoder::_EncodeAudio(const void* _buffer, int64 frameCount,
media_encode_info* info)
{
TRACE("AVCodecEncoder::_EncodeAudio(%p, %" B_PRId64 ", %p)\n", _buffer, frameCount, info);
if (fChunkBuffer == NULL)
return B_NO_MEMORY;
status_t ret = B_OK;
const uint8* buffer = reinterpret_cast<const uint8*>(_buffer);
size_t inputSampleSize = fInputFormat.u.raw_audio.format
& media_raw_audio_format::B_AUDIO_SIZE_MASK;
size_t inputFrameSize = inputSampleSize
* fInputFormat.u.raw_audio.channel_count;
size_t bufferSize = frameCount * inputFrameSize;
bufferSize = min_c(bufferSize, kDefaultChunkBufferSize);
if (fCodecContext->frame_size > 1) {
#if LIBAVCODEC_VERSION_MAJOR >= 60
if (av_fifo_grow2(fAudioFifo, bufferSize) < 0) {
TRACE(" av_fifo_grow2() failed\n");
return B_NO_MEMORY;
}
av_fifo_write(fAudioFifo, const_cast<uint8*>(buffer), bufferSize);
#else
if (av_fifo_realloc2(fAudioFifo, av_fifo_size(fAudioFifo) + bufferSize) < 0) {
TRACE(" av_fifo_realloc2() failed\n");
return B_NO_MEMORY;
}
av_fifo_generic_write(fAudioFifo, const_cast<uint8*>(buffer), bufferSize, NULL);
#endif
size_t frameBytes = fCodecContext->frame_size * inputFrameSize;
uint8* tempBuffer = new(std::nothrow) uint8[frameBytes];
if (tempBuffer == NULL)
return B_NO_MEMORY;
#if LIBAVCODEC_VERSION_MAJOR >= 60
while (av_fifo_can_read(fAudioFifo) >= frameBytes) {
av_fifo_read(fAudioFifo, tempBuffer, frameBytes);
#else
while (av_fifo_size(fAudioFifo) >= (int32)frameBytes) {
av_fifo_generic_read(fAudioFifo, tempBuffer, frameBytes, NULL);
#endif
ret = _EncodeAudio(tempBuffer, frameBytes, fCodecContext->frame_size,
info);
if (ret != B_OK)
break;
}
delete[] tempBuffer;
} else {
return _EncodeAudio(buffer, bufferSize, frameCount,
info);
}
return ret;
}
status_t
AVCodecEncoder::_EncodeAudio(const uint8* buffer, size_t bufferSize,
int64 frameCount, media_encode_info* info)
{
status_t ret;
AVPacket* packet = av_packet_alloc();
packet->data = NULL;
packet->size = 0;
int gotPacket = 0;
if (buffer) {
av_frame_unref(fFrame);
fFrame->nb_samples = frameCount;
int count = avcodec_fill_audio_frame(fFrame, get_channel_count(fCodecContext),
fCodecContext->sample_fmt, (const uint8_t*)buffer, bufferSize, 1);
if (count < 0) {
TRACE(" avcodec_encode_audio() failed filling data: %d\n", count);
av_packet_free(&packet);
return B_ERROR;
}
fFrame->pts = (bigtime_t)(fFramesWritten * 1000000LL
/ fInputFormat.u.raw_audio.frame_rate);
fFramesWritten += fFrame->nb_samples;
ret = avcodec_send_frame(fCodecContext, fFrame);
gotPacket = avcodec_receive_packet(fCodecContext, packet) == 0;
} else {
ret = avcodec_receive_packet(fCodecContext, packet);
gotPacket = (ret == 0);
}
if (buffer && fFrame->extended_data != fFrame->data)
av_freep(&fFrame->extended_data);
if (ret != 0) {
TRACE(" avcodec_encode_audio() failed: %s\n", strerror(ret));
av_packet_free(&packet);
return B_ERROR;
}
fFramesWritten += frameCount;
if (gotPacket) {
info->start_time = packet->pts;
if (packet->flags & AV_PKT_FLAG_KEY)
info->flags = B_MEDIA_KEY_FRAME;
else
info->flags = 0;
ret = WriteChunk(packet->data, packet->size, info);
if (ret != B_OK) {
TRACE(" error writing chunk: %s\n", strerror(ret));
av_packet_free(&packet);
return ret;
}
}
av_packet_free(&packet);
return B_OK;
}
status_t
AVCodecEncoder::_EncodeVideo(const void* buffer, int64 frameCount,
media_encode_info* info)
{
TRACE_IO("AVCodecEncoder::_EncodeVideo(%p, %lld, %p)\n", buffer, frameCount,
info);
if (fChunkBuffer == NULL)
return B_NO_MEMORY;
AVPacket* pkt = av_packet_alloc();
while (frameCount > 0) {
int bpr = fInputFormat.u.raw_video.display.bytes_per_row;
size_t bufferSize = fInputFormat.u.raw_video.display.line_count * bpr;
const uint8_t* buf8 = (const uint8_t*)buffer;
sws_scale(fSwsContext, &buf8, &bpr, 0,
fInputFormat.u.raw_video.display.line_count, fFrame->data,
fFrame->linesize);
if (_EncodeVideoFrame(fFrame, pkt, info) == B_OK) {
frameCount--;
fFramesWritten++;
buffer = (const void*)((const uint8*)buffer + bufferSize);
}
}
av_packet_free(&pkt);
return B_OK;
}
status_t
AVCodecEncoder::_EncodeVideoFrame(AVFrame* frame, AVPacket* pkt, media_encode_info* info)
{
int result = avcodec_send_frame(fCodecContext, frame);
if (result < 0) {
TRACE(" avcodec_send_frame() failed: %d\n", result);
return B_ERROR;
}
if (frame != NULL)
frame->pts++;
while (result == 0) {
result = avcodec_receive_packet(fCodecContext, pkt);
if (result == 0) {
TRACE(" avcodec_receive_packet: received one packet\n");
if (pkt->pts != AV_NOPTS_VALUE) {
TRACE(" codec frame PTS: %" B_PRId64 " (codec time_base: %d/%d)\n",
pkt->pts, fCodecContext->time_base.num,
fCodecContext->time_base.den);
} else {
TRACE(" codec frame PTS: N/A (codec time_base: %d/%d)\n",
fCodecContext->time_base.num, fCodecContext->time_base.den);
}
info->start_time = (bigtime_t)(fFramesWritten * 1000000LL
/ fInputFormat.u.raw_video.field_rate);
info->flags = 0;
if (pkt->flags & AV_PKT_FLAG_KEY)
info->flags |= B_MEDIA_KEY_FRAME;
result = WriteChunk(pkt->data, pkt->size, info);
if (result != B_OK) {
TRACE(" error writing chunk: %s\n", strerror(result));
break;
}
}
av_packet_unref(pkt);
}
if (result == AVERROR(EAGAIN))
return B_OK;
TRACE(" _EncodeVideoFrame(): returning...\n");
return result;
}