/*
 * 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"
 
 
//#define TRACE_AVFORMAT_WRITER
#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;
	// TODO: This could depend on the BMediaFile creation flags, IIRC,
	// they allow to specify a buffering mode.
 
typedef AVCodecID CodecID;
 
// #pragma mark - AVFormatWriter::StreamCookie
 
 
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;
			// Since different threads may write to the target,
			// we need to protect the file position and I/O by a lock.
			BLocker*			fStreamLock;
};
 
 
 
AVFormatWriter::StreamCookie::StreamCookie(AVFormatContext* context,
		BLocker* streamLock)
	:
	fFormatContext(context),
	fStream(NULL),
	fStreamLock(streamLock)
{
	av_init_packet(&fPacket);
}
 
 
AVFormatWriter::StreamCookie::~StreamCookie()
{
}
 
 
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;
 
//	TRACE("  fStream->codecpar: %p\n", fStream->codecpar);
	// TODO: This is a hack for now! Use avcodec_find_encoder_by_name()
	// or something similar...
	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);
 
	// Setup the stream according to the media 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;
 
		// video size
		fStream->codecpar->width = format->u.raw_video.display.line_width;
		fStream->codecpar->height = format->u.raw_video.display.line_count;
		// pixel aspect ratio
		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;
 
		// Use the last supported pixel format of the AVCodec, which we hope
		// is the one with the best quality (true for all currently supported
		// encoders).
//		AVCodec* codec = fStream->codecpar->codec;
//		for (int i = 0; codec->pix_fmts[i] != PIX_FMT_NONE; i++)
//			fStream->codecpar->pix_fmt = codec->pix_fmts[i];
		fStream->codecpar->format = AV_PIX_FMT_YUV420P;
 
	} else if (format->type == B_MEDIA_RAW_AUDIO) {
		fStream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
 
		// frame rate
		fStream->codecpar->sample_rate = (int)format->u.raw_audio.frame_rate;
 
		// channels
		fStream->codecpar->channels = format->u.raw_audio.channel_count;
 
		// set fStream to the audio format we want to use. This is only a hint
		// (each encoder has a different set of accepted formats)
		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;
		}
 
		// Now negociate the actual format with the encoder
		// First check if the requested format is acceptable
		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 not, force one of the acceptable ones
		if (*p == -1) {
			fStream->codecpar->format = codec->sample_fmts[0];
 
			// And finally set the format struct to the accepted format. It is
			// then up to the caller to make sure we get data matching that
			// format.
			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 (format->u.raw_audio.channel_mask == 0) {
			// guess the channel mask...
			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 {
			// The bits match 1:1 for media_multi_channels and FFmpeg defines.
			fStream->codecpar->channel_layout = format->u.raw_audio.channel_mask;
		}
	}
 
	TRACE("  stream->time_base: (%d/%d), codec->time_base: (%d/%d))\n",
		fStream->time_base.num, fStream->time_base.den,
		fStream->codec->time_base.num, fStream->codec->time_base.den);
 
#if 0
	// Write the AVCodecContext pointer to the user data section of the
	// media_format. For some encoders, it seems to be necessary to use
	// the AVCodecContext of the AVStream in order to successfully encode
	// anything and write valid media files. For example some codecs need
	// to store meta data or global data in the container.
	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: %lld)\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: %lld (stream->time_base: (%d/%d), "
		"codec->time_base: (%d/%d))\n", fPacket.pts,
		fStream->time_base.num, fStream->time_base.den,
		fStream->codec->time_base.num, fStream->codec->time_base.den);
 
#if 0
	// TODO: Eventually, we need to write interleaved packets, but
	// maybe we are only supposed to use this if we have actually
	// more than one stream. For the moment, this crashes in AVPacket
	// shuffling inside libavformat. Maybe if we want to use this, we
	// need to allocate a separate AVPacket and copy the chunk buffer.
	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(%lu, %p, %ld, %lu)\n",
		code, data, size, flags);
 
	BAutolock _(fStreamLock);
 
	return B_NOT_SUPPORTED;
}
 
 
// #pragma mark - AVFormatWriter
 
 
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");
 
	// Free the streams and close the AVCodecContexts
	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);
}
 
 
// #pragma mark -
 
 
status_t
AVFormatWriter::Init(const media_file_format* fileFormat)
{
	TRACE("AVFormatWriter::Init()\n");
 
	uint8* buffer = static_cast<uint8*>(av_malloc(kIOBufferSize));
	if (buffer == NULL)
		return B_NO_MEMORY;
 
	// Allocate I/O context and initialize it with buffer
	// and hook functions, pass ourself as cookie.
	fIOContext = avio_alloc_context(buffer, kIOBufferSize, 1, this,
			0, _Write, _Seek);
	if (fIOContext == NULL) {
		TRACE("av_alloc_put_byte() failed!\n");
		return B_ERROR;
	}
 
	// Setup I/O hooks. This seems to be enough.
	fFormatContext->pb = fIOContext;
 
	// Set the AVOutputFormat according to fileFormat...
	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;
 
	// We need to close the codecs we opened, even in case of failure.
	fCodecOpened = true;
 
	fHeaderError = avformat_write_header(fFormatContext, NULL);
	if (fHeaderError < 0)
		TRACE("  avformat_write_header(): %d\n", fHeaderError);
 
	#ifdef TRACE_AVFORMAT_WRITER
	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), codec->time_base: (%d/%d)\n",
			i, stream->time_base.num, stream->time_base.den,
			stream->codec->time_base.num, stream->codec->time_base.den);
	}
	#endif // TRACE_AVFORMAT_WRITER
 
	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;
 
	// From ffmpeg documentation: [av_write_trailer] may only be called
	// after a successful call to avformat_write_header.
	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;
}
 
 
// #pragma mark -
 
 
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(%lu, %p, %ld, %lu)\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);
}
 
 
// #pragma mark - I/O hooks
 
 
/*static*/ 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;
 
}
 
 
/*static*/ 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;
 
	// Support for special file size retrieval API without seeking anywhere:
	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;
}
 
 

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fStreams.