* Copyright 2009-2010, Stephan Aßmus <superstippi@gmx.de>
* Copyright 2018, Dario Casalinuovo
* All rights reserved. Distributed under the terms of the GNU L-GPL license.
*/
#include "AVFormatWriter.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <new>
#include <Application.h>
#include <AutoDeleter.h>
#include <Autolock.h>
#include <ByteOrder.h>
#include <MediaIO.h>
#include <MediaDefs.h>
#include <MediaFormats.h>
#include <Roster.h>
extern "C" {
#include "avformat.h"
}
#include "DemuxerTable.h"
#include "EncoderTable.h"
#include "gfx_util.h"
#ifdef TRACE_AVFORMAT_WRITER
# define TRACE printf
# define TRACE_IO(a...)
# define TRACE_PACKET printf
#else
# define TRACE(a...)
# define TRACE_IO(a...)
# define TRACE_PACKET(a...)
#endif
#define ERROR(a...) fprintf(stderr, a)
static const size_t kIOBufferSize = 64 * 1024;
typedef AVCodecID CodecID;
class AVFormatWriter::StreamCookie {
public:
StreamCookie(AVFormatContext* context,
BLocker* streamLock);
virtual ~StreamCookie();
status_t Init(media_format* format,
const media_codec_info* codecInfo);
status_t WriteChunk(const void* chunkBuffer,
size_t chunkSize,
media_encode_info* encodeInfo);
status_t AddTrackInfo(uint32 code, const void* data,
size_t size, uint32 flags);
private:
AVFormatContext* fFormatContext;
AVStream* fStream;
AVPacket* fPacket;
BLocker* fStreamLock;
};
AVFormatWriter::StreamCookie::StreamCookie(AVFormatContext* context,
BLocker* streamLock)
:
fFormatContext(context),
fStream(NULL),
fStreamLock(streamLock)
{
fPacket = av_packet_alloc();
}
AVFormatWriter::StreamCookie::~StreamCookie()
{
av_packet_free(&fPacket);
}
static void
set_channel_count(AVCodecParameters* context, int count)
{
#if LIBAVCODEC_VERSION_MAJOR >= 60
context->ch_layout.nb_channels = count;
#else
context->channels = count;
#endif
}
status_t
AVFormatWriter::StreamCookie::Init(media_format* format,
const media_codec_info* codecInfo)
{
TRACE("AVFormatWriter::StreamCookie::Init()\n");
BAutolock _(fStreamLock);
fPacket->stream_index = fFormatContext->nb_streams;
fStream = avformat_new_stream(fFormatContext, NULL);
if (fStream == NULL) {
TRACE(" failed to add new stream\n");
return B_ERROR;
}
fStream->id = fPacket->stream_index;
fStream->codecpar->codec_id = (CodecID)codecInfo->sub_id;
if (fStream->codecpar->codec_id == AV_CODEC_ID_NONE)
fStream->codecpar->codec_id = raw_audio_codec_id_for(*format);
if (format->type == B_MEDIA_RAW_VIDEO) {
fStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
fStream->time_base.den = (int)format->u.raw_video.field_rate;
fStream->time_base.num = 1;
fStream->codecpar->width = format->u.raw_video.display.line_width;
fStream->codecpar->height = format->u.raw_video.display.line_count;
fStream->sample_aspect_ratio.num
= format->u.raw_video.pixel_width_aspect;
fStream->sample_aspect_ratio.den
= format->u.raw_video.pixel_height_aspect;
if (fStream->sample_aspect_ratio.num == 0
|| fStream->sample_aspect_ratio.den == 0) {
av_reduce(&fStream->sample_aspect_ratio.num,
&fStream->sample_aspect_ratio.den, fStream->codecpar->width,
fStream->codecpar->height, 255);
}
fStream->codecpar->sample_aspect_ratio = fStream->sample_aspect_ratio;
fStream->codecpar->format = AV_PIX_FMT_YUV420P;
} else if (format->type == B_MEDIA_RAW_AUDIO) {
fStream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
fStream->codecpar->sample_rate = (int)format->u.raw_audio.frame_rate;
set_channel_count(fStream->codecpar, format->u.raw_audio.channel_count);
switch (format->u.raw_audio.format) {
case media_raw_audio_format::B_AUDIO_FLOAT:
fStream->codecpar->format = AV_SAMPLE_FMT_FLT;
break;
case media_raw_audio_format::B_AUDIO_DOUBLE:
fStream->codecpar->format = AV_SAMPLE_FMT_DBL;
break;
case media_raw_audio_format::B_AUDIO_INT:
fStream->codecpar->format = AV_SAMPLE_FMT_S32;
break;
case media_raw_audio_format::B_AUDIO_SHORT:
fStream->codecpar->format = AV_SAMPLE_FMT_S16;
break;
case media_raw_audio_format::B_AUDIO_UCHAR:
fStream->codecpar->format = AV_SAMPLE_FMT_U8;
break;
case media_raw_audio_format::B_AUDIO_CHAR:
default:
return B_MEDIA_BAD_FORMAT;
break;
}
const AVCodec* codec = avcodec_find_encoder(fStream->codecpar->codec_id);
if (codec == NULL)
return B_MEDIA_BAD_FORMAT;
const enum AVSampleFormat *p = codec->sample_fmts;
for (; *p != -1; p++) {
if (*p == fStream->codecpar->format)
break;
}
if (*p == -1) {
fStream->codecpar->format = codec->sample_fmts[0];
switch (fStream->codecpar->format) {
case AV_SAMPLE_FMT_FLT:
format->u.raw_audio.format
= media_raw_audio_format::B_AUDIO_FLOAT;
break;
case AV_SAMPLE_FMT_DBL:
format->u.raw_audio.format
= media_raw_audio_format::B_AUDIO_DOUBLE;
break;
case AV_SAMPLE_FMT_S32:
format->u.raw_audio.format
= media_raw_audio_format::B_AUDIO_INT;
break;
case AV_SAMPLE_FMT_S16:
format->u.raw_audio.format
= media_raw_audio_format::B_AUDIO_SHORT;
break;
case AV_SAMPLE_FMT_U8:
format->u.raw_audio.format
= media_raw_audio_format::B_AUDIO_UCHAR;
break;
default:
return B_MEDIA_BAD_FORMAT;
break;
}
}
#if LIBAVCODEC_VERSION_MAJOR >= 60
if (format->u.raw_audio.channel_mask == 0) {
av_channel_layout_default(&fStream->codecpar->ch_layout,
format->u.raw_audio.channel_count);
} else {
av_channel_layout_from_mask(&fStream->codecpar->ch_layout,
format->u.raw_audio.channel_mask);
}
#else
if (format->u.raw_audio.channel_mask == 0) {
switch (format->u.raw_audio.channel_count) {
default:
case 2:
fStream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
break;
case 1:
fStream->codecpar->channel_layout = AV_CH_LAYOUT_MONO;
break;
case 3:
fStream->codecpar->channel_layout = AV_CH_LAYOUT_SURROUND;
break;
case 4:
fStream->codecpar->channel_layout = AV_CH_LAYOUT_QUAD;
break;
case 5:
fStream->codecpar->channel_layout = AV_CH_LAYOUT_5POINT0;
break;
case 6:
fStream->codecpar->channel_layout = AV_CH_LAYOUT_5POINT1;
break;
case 8:
fStream->codecpar->channel_layout = AV_CH_LAYOUT_7POINT1;
break;
case 10:
fStream->codecpar->channel_layout = AV_CH_LAYOUT_7POINT1_WIDE;
break;
}
} else {
fStream->codecpar->channel_layout = format->u.raw_audio.channel_mask;
}
#endif
}
TRACE(" stream->time_base: (%d/%d)\n",
fStream->time_base.num, fStream->time_base.den);
#if 0
app_info appInfo;
if (be_app->GetAppInfo(&appInfo) == B_OK) {
uchar* userData = format->user_data;
*(uint32*)userData = 'ffmp';
userData += sizeof(uint32);
*(team_id*)userData = appInfo.team;
userData += sizeof(team_id);
*(AVCodecContext**)userData = fStream->codec;
}
#endif
return B_OK;
}
status_t
AVFormatWriter::StreamCookie::WriteChunk(const void* chunkBuffer,
size_t chunkSize, media_encode_info* encodeInfo)
{
TRACE_PACKET("AVFormatWriter::StreamCookie[%d]::WriteChunk(%p, %ld, "
"start_time: %" B_PRIdBIGTIME ")\n", fStream->index, chunkBuffer, chunkSize,
encodeInfo->start_time);
BAutolock _(fStreamLock);
fPacket->data = const_cast<uint8_t*>((const uint8_t*)chunkBuffer);
fPacket->size = chunkSize;
fPacket->stream_index = fStream->index;
fPacket->pts = int64_t((double)encodeInfo->start_time
* fStream->time_base.den / (1000000.0 * fStream->time_base.num)
+ 0.5);
fPacket->dts = fPacket->pts;
fPacket->flags = 0;
if ((encodeInfo->flags & B_MEDIA_KEY_FRAME) != 0)
fPacket->flags |= AV_PKT_FLAG_KEY;
TRACE_PACKET(" PTS: %" PRId64 " (stream->time_base: (%d/%d)\n", fPacket->pts,
fStream->time_base.num, fStream->time_base.den);
#if 0
int result = av_interleaved_write_frame(fFormatContext, fPacket);
if (result < 0)
TRACE(" av_interleaved_write_frame(): %d\n", result);
#else
int result = av_write_frame(fFormatContext, fPacket);
if (result < 0)
TRACE(" av_write_frame(): %d\n", result);
#endif
return result == 0 ? B_OK : B_ERROR;
}
status_t
AVFormatWriter::StreamCookie::AddTrackInfo(uint32 code,
const void* data, size_t size, uint32 flags)
{
TRACE("AVFormatWriter::StreamCookie::AddTrackInfo(%" B_PRIu32 ", %p, %ld, %" B_PRIu32 ")\n",
code, data, size, flags);
BAutolock _(fStreamLock);
return B_NOT_SUPPORTED;
}
AVFormatWriter::AVFormatWriter()
:
fFormatContext(avformat_alloc_context()),
fCodecOpened(false),
fHeaderError(-1),
fIOContext(NULL),
fStreamLock("stream lock")
{
TRACE("AVFormatWriter::AVFormatWriter\n");
}
AVFormatWriter::~AVFormatWriter()
{
TRACE("AVFormatWriter::~AVFormatWriter\n");
for (unsigned i = 0; i < fFormatContext->nb_streams; i++) {
av_freep(&fFormatContext->streams[i]->codecpar);
av_freep(&fFormatContext->streams[i]);
}
avformat_free_context(fFormatContext);
av_free(fIOContext->buffer);
av_free(fIOContext);
}
status_t
AVFormatWriter::Init(const media_file_format* fileFormat)
{
TRACE("AVFormatWriter::Init()\n");
if (fIOContext == NULL) {
uint8* buffer = static_cast<uint8*>(av_malloc(kIOBufferSize));
if (buffer == NULL)
return B_NO_MEMORY;
fIOContext = avio_alloc_context(buffer, kIOBufferSize, 1, this,
0, _Write, _Seek);
if (fIOContext == NULL) {
av_free(buffer);
TRACE("av_alloc_put_byte() failed!\n");
return B_ERROR;
}
fFormatContext->pb = fIOContext;
}
fFormatContext->oformat = av_guess_format(fileFormat->short_name,
fileFormat->file_extension, fileFormat->mime_type);
if (fFormatContext->oformat == NULL) {
TRACE(" failed to find AVOuputFormat for %s\n",
fileFormat->short_name);
return B_NOT_SUPPORTED;
}
TRACE(" found AVOuputFormat for %s: %s\n", fileFormat->short_name,
fFormatContext->oformat->name);
return B_OK;
}
status_t
AVFormatWriter::SetCopyright(const char* copyright)
{
TRACE("AVFormatWriter::SetCopyright(%s)\n", copyright);
return B_NOT_SUPPORTED;
}
status_t
AVFormatWriter::CommitHeader()
{
TRACE("AVFormatWriter::CommitHeader\n");
if (fFormatContext == NULL)
return B_NO_INIT;
if (fCodecOpened)
return B_NOT_ALLOWED;
fCodecOpened = true;
fHeaderError = avformat_write_header(fFormatContext, NULL);
#ifdef TRACE_AVFORMAT_WRITER
if (fHeaderError < 0) {
char errorBuffer[AV_ERROR_MAX_STRING_SIZE];
av_strerror(fHeaderError, errorBuffer, sizeof(errorBuffer));
TRACE(" avformat_write_header(): %s\n", errorBuffer);
} else {
TRACE(" wrote header\n");
}
for (unsigned i = 0; i < fFormatContext->nb_streams; i++) {
AVStream* stream = fFormatContext->streams[i];
TRACE(" stream[%u] time_base: (%d/%d)\n",
i, stream->time_base.num, stream->time_base.den);
}
#endif
return fHeaderError == 0 ? B_OK : B_ERROR;
}
status_t
AVFormatWriter::Flush()
{
TRACE("AVFormatWriter::Flush\n");
return B_NOT_SUPPORTED;
}
status_t
AVFormatWriter::Close()
{
TRACE("AVFormatWriter::Close\n");
if (fFormatContext == NULL)
return B_NO_INIT;
if (!fCodecOpened)
return B_NOT_ALLOWED;
if (fHeaderError != 0)
return B_ERROR;
int result = av_write_trailer(fFormatContext);
if (result < 0)
TRACE(" av_write_trailer(): %d\n", result);
return result == 0 ? B_OK : B_ERROR;
}
status_t
AVFormatWriter::AllocateCookie(void** _cookie, media_format* format,
const media_codec_info* codecInfo)
{
TRACE("AVFormatWriter::AllocateCookie()\n");
if (fCodecOpened)
return B_NOT_ALLOWED;
BAutolock _(fStreamLock);
if (_cookie == NULL)
return B_BAD_VALUE;
StreamCookie* cookie = new(std::nothrow) StreamCookie(fFormatContext,
&fStreamLock);
status_t ret = cookie->Init(format, codecInfo);
if (ret != B_OK) {
delete cookie;
return ret;
}
*_cookie = cookie;
return B_OK;
}
status_t
AVFormatWriter::FreeCookie(void* _cookie)
{
BAutolock _(fStreamLock);
StreamCookie* cookie = reinterpret_cast<StreamCookie*>(_cookie);
delete cookie;
return B_OK;
}
status_t
AVFormatWriter::SetCopyright(void* cookie, const char* copyright)
{
TRACE("AVFormatWriter::SetCopyright(%p, %s)\n", cookie, copyright);
return B_NOT_SUPPORTED;
}
status_t
AVFormatWriter::AddTrackInfo(void* _cookie, uint32 code,
const void* data, size_t size, uint32 flags)
{
TRACE("AVFormatWriter::AddTrackInfo(%" B_PRIu32 ", %p, %ld, %" B_PRIu32 ")\n",
code, data, size, flags);
if (fHeaderError != 0)
return B_ERROR;
StreamCookie* cookie = reinterpret_cast<StreamCookie*>(_cookie);
return cookie->AddTrackInfo(code, data, size, flags);
}
status_t
AVFormatWriter::WriteChunk(void* _cookie, const void* chunkBuffer,
size_t chunkSize, media_encode_info* encodeInfo)
{
TRACE_PACKET("AVFormatWriter::WriteChunk(%p, %ld, %p)\n", chunkBuffer,
chunkSize, encodeInfo);
if (fHeaderError != 0)
return B_ERROR;
StreamCookie* cookie = reinterpret_cast<StreamCookie*>(_cookie);
return cookie->WriteChunk(chunkBuffer, chunkSize, encodeInfo);
}
int
AVFormatWriter::_Write(void* cookie, uint8* buffer, int bufferSize)
{
TRACE_IO("AVFormatWriter::_Write(%p, %p, %d)\n",
cookie, buffer, bufferSize);
AVFormatWriter* writer = reinterpret_cast<AVFormatWriter*>(cookie);
ssize_t written = writer->fTarget->Write(buffer, bufferSize);
TRACE_IO(" written: %ld\n", written);
return (int)written;
}
off_t
AVFormatWriter::_Seek(void* cookie, off_t offset, int whence)
{
TRACE_IO("AVFormatWriter::_Seek(%p, %lld, %d)\n",
cookie, offset, whence);
AVFormatWriter* writer = reinterpret_cast<AVFormatWriter*>(cookie);
BMediaIO* mediaIO = dynamic_cast<BMediaIO*>(writer->fTarget);
if (mediaIO == NULL)
return -1;
if (whence == AVSEEK_SIZE) {
off_t size;
if (mediaIO->GetSize(&size) == B_OK)
return size;
return -1;
}
off_t position = mediaIO->Seek(offset, whence);
TRACE_IO(" position: %lld\n", position);
if (position < 0)
return -1;
return position;
}