/////////////////////////////////////////////////////////////////////////////
// Name:        mediatrc_ffmpeg.cpp
// Purpose:     FFMPEG Media Transcoder
// Author:      Alex Thuering
// Created:     26.04.2008
// RCS-ID:      $Id: mediatrc_ffmpeg.cpp,v 1.22 2010/05/03 06:06:48 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "mediatrc_ffmpeg.h"
#include <wx/wx.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <errno.h>
extern "C" {
#ifdef HAVE_FFMPEG_AVUTIL_H
#include <opt.h>
#include <fifo.h>
#include <swscale.h>
#include <avutil.h>
#else
#include <libavcodec/opt.h>
#include <libswscale/swscale.h>
#include <libavutil/avutil.h>
#include <libavutil/fifo.h>
#endif
}

#define AUDIO_BUF_SIZE 50000
#define VIDEO_BUF_SIZE 1835008
#define QSCALE_NONE -99999
#define MAX_AUDIO_PACKET_SIZE (128 * 1024)
#define MAKE_SFMT_PAIR(a,b) ((a)+SAMPLE_FMT_NB*(b))

#ifndef FF_ARRAY_ELEMS
#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
#endif

struct AVInputStream;

typedef struct AVOutputStream {
	int file_index; /* file index */
	int index; /* stream index in the output file */
	int source_index; /* AVInputStream index */
	AVStream *st; /* stream in the output file */
	int encoding_needed; /* true if encoding needed for this stream */
	int frame_number;
	/* input pts and corresponding output pts
	 for A/V sync */
	//double sync_ipts;        /* dts from the AVPacket of the demuxer in second units */
	struct AVInputStream *sync_ist; /* input stream to sync against */
	int64_t sync_opts; /* output frame counter, could be changed to some true timestamp *///FIXME look at frame_number
	/* video only */
	int video_resample;
	AVFrame pict_tmp; /* temporary image for resampling */
	struct SwsContext *img_resample_ctx; /* for image resampling */
	int resample_height;

	int video_crop;
	int topBand; /* cropping area sizes */
	int leftBand;

	int video_pad;
	int padtop; /* padding area sizes */
	int padbottom;
	int padleft;
	int padright;

	/* audio only */
	int audio_resample;
	ReSampleContext *resample; /* for audio resampling */
	int reformat_pair;
	//AVAudioConvert *reformat_ctx;
	AVFifoBuffer* fifo; /* for compression: one audio fifo per codec */
	FILE *logfile;
} AVOutputStream;

typedef struct AVInputStream {
	int file_index;
	int index;
	AVStream *st;
	int discard; /* true if stream data should be discarded */
	int decoding_needed; /* true if the packets must be decoded in 'raw_fifo' */
	int64_t sample_index; /* current sample */

	int64_t start; /* time when read started */
	unsigned long frame; /* current frame */
	int64_t next_pts; /* synthetic pts for cases where pkt.pts
	 is not defined */
	int64_t pts; /* current pts */
	int is_start; /* is 1 at the start and after a discontinuity */
} AVInputStream;

typedef struct AVInputFile {
	int eof_reached; /* true if eof reached */
	int ist_index; /* index of first stream in ist_table */
	int buffer_size; /* current total buffer size */
	int nb_streams; /* nb streams we are aware of */
} AVInputFile;

size_t ff_strlcpy(char *dst, const char *src, size_t size) {
	size_t len = 0;
	while (++len < size && *src)
		*dst++ = *src++;
	if (len <= size)
		*dst = 0;
	return len + strlen(src) - 1;
}

int ff_strstart(const char *str, const char *pfx, const char **ptr) {
	while (*pfx && *pfx == *str) {
		pfx++;
		str++;
	}
	if (!*pfx && ptr)
		*ptr = str;
	return !*pfx;
}

double psnr(double d) {
	return -10.0*log(d)/log(10.0);
}

void av_log_wxLog_callback(void* ptr, int level, const char* fmt, va_list vl) {
	if (level>av_log_get_level())
		return;
	static wxString buf;
	char s[1024];
	vsnprintf(s, 1024, fmt, vl);
	buf += wxString(s, wxConvLocal);
	if (buf.GetChar(buf.Length()-1) == wxT('\n')) {
		wxLogMessage(buf.SubString(0, buf.Length()-2));
		buf = wxT("");
	}
}

wxFfmpegMediaTranscoder::wxFfmpegMediaTranscoder(int threadCount) {
	nb_input_files = 0;
	nb_output_files = 0;
	nb_ocodecs = 0;
	nb_stream_maps = 0;
	nb_meta_data_maps = 0;
	frame_width = 0;
	frame_height = 0;
	frame_aspect_ratio = 0;
	frame_pix_fmt = PIX_FMT_NONE;
	audio_sample_fmt = SAMPLE_FMT_NONE;
	frame_padtop = 0;
	frame_padbottom = 0;
	frame_padleft = 0;
	frame_padright = 0;
	padcolor[0] = 16;
	padcolor[1] = 128;
	padcolor[2] = 128; // default to black
	frame_topBand = 0;
	frame_bottomBand = 0;
	frame_leftBand = 0;
	frame_rightBand = 0;
	max_frames[0] = INT_MAX;
	max_frames[1] = INT_MAX;
	max_frames[2] = INT_MAX;
	max_frames[3] = INT_MAX;
	frame_rate = (AVRational) {0,0};
	video_qscale = 0;
	intra_matrix = NULL;
	inter_matrix = NULL;
	video_rc_override_string = NULL;
	video_codec_tag = 0;
	same_quality = 0;
	do_deinterlace = 0;
	top_field_first = -1;
	me_threshold = 0;
	intra_dc_precision = 8;
	loop_input = 0;
	loop_output = AVFMT_NOOUTPUTLOOP;
	qp_hist = 0;

	intra_only = 0;
	audio_sample_rate = 44100;
	channel_layout = 0;
	audio_qscale = QSCALE_NONE;
	audio_channels = 1;
	audio_codec_tag = 0;
	audio_language = NULL;

	subtitle_language = NULL;
	subtitle_codec_tag = 0;

	mux_preload= 0.5;
	mux_max_delay= 0.7;

	//recording_time = INT64_MAX;
	start_time = 0;
	rec_timestamp = 0;
	input_ts_offset = 0;
	metadata_count = 0;
	metadata = NULL;
	do_psnr = 0;
	do_pass = 0;
	pass_logfilename_prefix = "ffmpeg2pass";
	video_sync_method= -1;
	audio_sync_method= 0;
	audio_drift_threshold= 0.1;
	copy_ts= 0;
	opt_shortest = 0; //
	video_global_header = 0;
	vstats_filename = NULL;
	vstats_file = NULL;
	opt_programid = 0;
	copy_initial_nonkeyframes = 0;

	rate_emu = 0;

	video_channel = 0;
	video_standard = NULL;

	audio_volume = 256;

	exit_on_error = 0;
	verbose = 1;
	thread_count = threadCount;
	q_pressed = 0;
	video_size = 0;
	audio_size = 0;
	extra_size = 0;
	nb_frames_dup = 0;
	nb_frames_drop = 0;
	input_sync = 0;
	limit_filesize = 0;
	force_fps = 0;

	pgmyuv_compatibility_hack = 0;
	dts_delta_threshold = 10;

	sws_flags = SWS_BICUBIC;

	video_bitstream_filters = NULL;
	audio_bitstream_filters = NULL;
	subtitle_bitstream_filters = NULL;
	av_log_set_callback(&av_log_wxLog_callback);
}

wxFfmpegMediaTranscoder::~wxFfmpegMediaTranscoder() {
	End();
	av_log_set_callback(&av_log_default_callback);
}

bool wxFfmpegMediaTranscoder::AddInputFile(const wxString& fileName,
		bool disableVideo, bool disableAudio, bool disableSubtitle) {
	AVFormatContext *ic;
	int err, i, ret, rfps, rfps_base;
	int64_t timestamp;

	/* get default parameters from command line */
	ic = avformat_alloc_context();
	ic->flags |= AVFMT_FLAG_NONBLOCK;

	// open the input file with generic libav function
	err = av_open_input_file(&ic, (const char*) fileName.mb_str(), NULL, 0, NULL);
	if (err < 0) {
		PrintError(fileName, err);
		return false;
	}

	ic->loop_input = loop_input;

	/* If not enough info to get the stream parameters, we decode the
	 first frames to get it. (used in mpeg case for example) */
	ret = av_find_stream_info(ic);
	if (ret < 0 && verbose >= 0) {
		wxLogError(wxT("%s: could not find codec parameters"), fileName.c_str());
		return false;
	}

	timestamp = start_time;
	/* add the stream start time */
	if (ic->start_time != (int64_t) AV_NOPTS_VALUE)
		timestamp += ic->start_time;

	/* if seeking requested, we execute it */
	if (start_time != 0) {
		ret = av_seek_frame(ic, -1, timestamp, AVSEEK_FLAG_BACKWARD);
		if (ret < 0) {
			wxLogError(wxT("%s: could not seek to position %0.3f"),
			fileName.c_str(), (double)timestamp / AV_TIME_BASE);
		}
		/* reset seek info */
		start_time = 0;
	}

	/* update the current parameters so that they match the one of the input stream */
	for (i=0; i<(int)ic->nb_streams; i++) {
		AVCodecContext *enc = ic->streams[i]->codec;
		if (thread_count>1)
			avcodec_thread_init(enc, thread_count);
		enc->thread_count= thread_count;
		switch (enc->codec_type) {
		case CODEC_TYPE_AUDIO:
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 5, 0)
			channel_layout = enc->channel_layout;
#endif
			audio_channels = enc->channels;
			audio_sample_rate = enc->sample_rate;
			audio_sample_fmt = enc->sample_fmt;
			if (disableAudio)
				ic->streams[i]->discard = AVDISCARD_ALL;
			break;
		case CODEC_TYPE_VIDEO:
			frame_height = enc->height;
			frame_width = enc->width;
			if(ic->streams[i]->sample_aspect_ratio.num)
                frame_aspect_ratio=av_q2d(ic->streams[i]->sample_aspect_ratio);
            else
                frame_aspect_ratio=av_q2d(enc->sample_aspect_ratio);
            frame_aspect_ratio *= (float) enc->width / enc->height; 
			frame_pix_fmt = enc->pix_fmt;
			rfps = ic->streams[i]->r_frame_rate.num;
			rfps_base = ic->streams[i]->r_frame_rate.den;
			if (enc->lowres)
				enc->flags |= CODEC_FLAG_EMU_EDGE;
			if (me_threshold)
				enc->debug |= FF_DEBUG_MV;

			if (enc->time_base.den != rfps || enc->time_base.num != rfps_base) {

				if (verbose >= 0)
					wxLogMessage(wxT("\nSeems stream %d codec frame rate differs from container frame rate: %2.2f (%d/%d) -> %2.2f (%d/%d)"),
					i, (float)enc->time_base.den / enc->time_base.num, enc->time_base.den, enc->time_base.num,
					(float)rfps / rfps_base, rfps, rfps_base);
			}
			/* update the current frame rate to match the stream frame rate */
			frame_rate.num = rfps;
			frame_rate.den = rfps_base;

			enc->rate_emu = rate_emu;
			if (disableVideo)
				ic->streams[i]->discard = AVDISCARD_ALL;
			break;
		case CODEC_TYPE_DATA:
			break;
		case CODEC_TYPE_SUBTITLE:
			if (disableSubtitle)
				ic->streams[i]->discard = AVDISCARD_ALL;
			break;
		case CODEC_TYPE_ATTACHMENT:
		case CODEC_TYPE_UNKNOWN:
			break;
		default:
			wxLogError(_("Unknown codec type"));
			return false;
		}
	}

	input_files[nb_input_files] = ic;
	input_files_ts_offset[nb_input_files] = input_ts_offset - (copy_ts ? 0
			: timestamp);
	/* dump the file content */
	if (verbose >= 0)
		dump_format(ic, nb_input_files, (const char*)fileName.mb_str(), 0);

	nb_input_files++;

	video_channel = 0;

	rate_emu = 0;
	return true;
}

bool wxFfmpegMediaTranscoder::SetOutputFile(const wxString& fileName,
		VideoFormat videoFormat, AudioFormat audioFormat, SubtitleFormat subtitleFormat,
		int videoBitrate, bool vbr, int audioBitrate) {
	wxArrayInt audioFormats;
	if (audioFormat != afNONE)
		audioFormats.Add(audioFormat);
	wxArrayInt subtitleFormats;
	subtitleFormats.Add(subtitleFormat);
	return SetOutputFile(fileName, videoFormat, audioFormats, subtitleFormats,
			videoBitrate);
}

bool wxFfmpegMediaTranscoder::SetOutputFile(const wxString& fileName,
		VideoFormat videoFormat, wxArrayInt audioFormats, wxArrayInt subtitleFormats,
		int videoBitrate, bool vbr, int audioBitrate) {
	AVFormatParameters params, *ap = &params;

	AVOutputFormat *outputFormat = NULL;
	if (audioFormats.GetCount() == 0)
		outputFormat = guess_format("mpeg2video", NULL, NULL);
	else if (videoFormat == vfNONE)
		outputFormat = guess_format("mp2", NULL, NULL);
	else
		outputFormat = guess_format("dvd", NULL, NULL);
	if (!outputFormat) {
		wxLogError(wxT("Error setting output format to DVD."));
		return false;
	}

	AVFormatContext *oc = avformat_alloc_context();

	oc->oformat = outputFormat;
	ff_strlcpy(oc->filename, (const char*) fileName.mb_str(),
			sizeof(oc->filename));

	// disable if no corresponding type found and at least one input file
	if (nb_input_files == 0) {
		wxLogError(wxT("Please add input files"));
		return false;
	}
	// add video, audio and subtitle streams
	if (videoFormat != vfNONE && !AddVideoStream(oc, videoFormat, videoBitrate, vbr))
		return false;
	for (unsigned int afIdx = 0; afIdx < audioFormats.GetCount(); afIdx++)
		if (audioFormats[afIdx] != afNONE && !AddAudioStream(oc, (AudioFormat) audioFormats[afIdx], audioBitrate))
			return false;
	for (unsigned int sfIdx = 0; sfIdx < subtitleFormats.GetCount(); sfIdx++)
		if (subtitleFormats[sfIdx] != sfNONE && !AddSubtitleStream(oc, (SubtitleFormat) subtitleFormats[sfIdx]))
			return false;

	oc->timestamp = rec_timestamp;

	for(; metadata_count>0; metadata_count--){
		av_metadata_set(&oc->metadata, metadata[metadata_count-1].key,
				metadata[metadata_count-1].value);
	}
	av_metadata_conv(oc, oc->oformat->metadata_conv, NULL);

	output_files[nb_output_files++] = oc;

	/* check filename in case of an image number is expected */
	if (oc->oformat->flags & AVFMT_NEEDNUMBER) {
		if (!av_filename_number_test(oc->filename)) {
			PrintError(fileName, AVERROR_NUMEXPECTED);
			return false;
		}
	}

	/* test if it already exists to avoid loosing precious files */
	if (url_exist((const char*) fileName.mb_str())) {
		wxLogError(wxT("File '%s' already exists"), fileName.c_str());
		return false;
	}

	/* open the file */
	if (url_fopen(&oc->pb, (const char*) fileName.mb_str(), URL_WRONLY) < 0) {
		wxLogError(wxT("Could not open '%s'"), fileName.c_str());
		return false;
	}

	memset(ap, 0, sizeof(*ap));
	if (av_set_parameters(oc, ap) < 0) {
		wxLogError(wxT("%s: Invalid encoding parameters"), fileName.c_str());
		return false;
	}

	oc->preload= (int)(mux_preload*AV_TIME_BASE);
	oc->max_delay= (int)(mux_max_delay*AV_TIME_BASE);
	oc->loop_output = loop_output;
	oc->flags |= AVFMT_FLAG_NONBLOCK;

	return true;
}

void wxFfmpegMediaTranscoder::PrintError(const wxString& msg, int err) {
	switch (err) {
	case AVERROR_NUMEXPECTED:
		wxLogError(wxT("%s: Incorrect image filename syntax.\n"
				"Use '%%d' to specify the image number:\n"
				"  for img1.jpg, img2.jpg, ..., use 'img%%d.jpg';\n"
				"  for img001.jpg, img002.jpg, ..., use 'img%%03d.jpg'."), msg.c_str());
		break;
	case AVERROR_INVALIDDATA:
		wxLogError(wxT("%s: Error while parsing header"), msg.c_str());
		break;
	case AVERROR_NOFMT:
		wxLogError(wxT("%s: Unknown format"), msg.c_str());
		break;
	case AVERROR(EIO):
		wxLogError(wxT("%s: I/O error occured\n"
				"Usually that means that input file is truncated and/or corrupted."), msg.c_str());
		break;
	case AVERROR(ENOMEM):
		wxLogError(wxT("%s: memory allocation error occured"), msg.c_str());
		break;
	case AVERROR(ENOENT):
		wxLogError(wxT("%s: no such file or directory"), msg.c_str());
		break;
	default:
		wxLogError(wxT("%s: Error while opening file"), msg.c_str());
		break;
	}
}

bool wxFfmpegMediaTranscoder::AddVideoStream(AVFormatContext *oc,
		VideoFormat videoFormat, int bitrate, bool vbr) {
	AVStream *st;
	AVCodecContext *video_enc;

	CodecID codecId = videoFormat == vfNONE ? CODEC_ID_NONE : CODEC_ID_MPEG2VIDEO;
	
	wxSize frameSize = GetFrameSize(videoFormat);
	frame_width = frameSize.GetWidth();
	frame_height = frameSize.GetHeight();
	frame_rate.num = isNTSC(videoFormat) ? 30000 : 25;
	frame_rate.den = isNTSC(videoFormat) ? 1001 : 1;

	st = av_new_stream(oc, oc->nb_streams);
	if (!st) {
		wxLogError(wxT("Could not alloc stream"));
		return false;
	}
	avcodec_get_context_defaults2(st->codec, CODEC_TYPE_VIDEO);
	bitstream_filters[nb_output_files][oc->nb_streams - 1]
			= video_bitstream_filters;
	video_bitstream_filters = NULL;

	if (thread_count>1)
		avcodec_thread_init(st->codec, thread_count);

	video_enc = st->codec;

	if (video_codec_tag)
		video_enc->codec_tag= video_codec_tag;

	if ((video_global_header&1) || (video_global_header==0
			&& (oc->oformat->flags & AVFMT_GLOBALHEADER))) {
		video_enc->flags |= CODEC_FLAG_GLOBAL_HEADER;
	}
	if (video_global_header&2) {
		video_enc->flags2 |= CODEC_FLAG2_LOCAL_HEADER;
	}

	if (videoFormat == vfCOPY) {
		st->stream_copy = 1;
		video_enc->codec_type = CODEC_TYPE_VIDEO;
		video_enc->sample_aspect_ratio = av_d2q(frame_aspect_ratio*frame_height/frame_width, 255); 
		st->sample_aspect_ratio = video_enc->sample_aspect_ratio;
	} else {
		const char *p;
		int i;
		
		AVCodec *codec = avcodec_find_encoder(codecId);
		AVRational fps= frame_rate.num ? frame_rate : (AVRational) {25,1};
		
		output_codecs[nb_ocodecs] = codec;
		
		av_set_int(video_enc, "gop", isNTSC(videoFormat) ? 15 : 12);
		av_set_int(video_enc, "b", bitrate * 1000);
		av_set_int(video_enc, "maxrate", vbr ? 9000000 : bitrate * 1000);
		av_set_int(video_enc, "minrate", vbr ? 0 : bitrate * 1000);
		av_set_int(video_enc, "bufsize", 1835008); //224*1024*8

		av_set_int(video_enc, "packetsize", 2048);
		av_set_int(video_enc, "muxrate", 10080000);
		
		video_enc->codec_id = codecId;
		
		if (codec && codec->supported_framerates && !force_fps)
			fps = codec->supported_framerates[av_find_nearest_q_idx(fps, codec->supported_framerates)];
		video_enc->time_base.den = fps.num;
		video_enc->time_base.num = fps.den;

		video_enc->width = frame_width + frame_padright + frame_padleft;
		video_enc->height = frame_height + frame_padtop + frame_padbottom;
		video_enc->sample_aspect_ratio = av_d2q(frame_aspect_ratio*video_enc->height/video_enc->width, 255);
		video_enc->pix_fmt = frame_pix_fmt;
		st->sample_aspect_ratio = video_enc->sample_aspect_ratio; 

		if (codec && codec->pix_fmts) {
			const enum PixelFormat *p= codec->pix_fmts;
			for (; *p!=-1; p++) {
				if (*p == video_enc->pix_fmt)
					break;
			}
			if (*p == -1)
				video_enc->pix_fmt = codec->pix_fmts[0];
		}

		if (intra_only)
			video_enc->gop_size = 0;
		if (video_qscale || same_quality) {
			video_enc->flags |= CODEC_FLAG_QSCALE;
			st->quality = FF_QP2LAMBDA * video_qscale;
			video_enc->global_quality = (int) st->quality;
		}

		if (intra_matrix)
			video_enc->intra_matrix = intra_matrix;
		if (inter_matrix)
			video_enc->inter_matrix = inter_matrix;
		
		video_enc->thread_count = thread_count;
		p= video_rc_override_string;
		for (i=0; p; i++) {
			int start, end, q;
			int e=sscanf(p, "%d,%d,%d", &start, &end, &q);
			if (e!=3) {
				wxLogError(wxT("error parsing rc_override"));
				return false;
			}
			video_enc->rc_override = (RcOverride*) av_realloc(
					video_enc->rc_override, sizeof(RcOverride)*(i+1));
			video_enc->rc_override[i].start_frame= start;
			video_enc->rc_override[i].end_frame = end;
			if (q>0) {
				video_enc->rc_override[i].qscale= q;
				video_enc->rc_override[i].quality_factor= 1.0;
			} else {
				video_enc->rc_override[i].qscale= 0;
				video_enc->rc_override[i].quality_factor= -q/100.0;
			}
			p= strchr(p, '/');
			if (p)
				p++;
		}
		video_enc->rc_override_count=i;
		if (!video_enc->rc_initial_buffer_occupancy)
			video_enc->rc_initial_buffer_occupancy = video_enc->rc_buffer_size*3/4;
		video_enc->me_threshold= me_threshold;
		video_enc->intra_dc_precision= intra_dc_precision - 8;

		if (do_psnr)
			video_enc->flags|= CODEC_FLAG_PSNR;

		/* two pass mode */
		if (do_pass) {
			if (do_pass == 1) {
				video_enc->flags |= CODEC_FLAG_PASS1;
			} else {
				video_enc->flags |= CODEC_FLAG_PASS2;
			}
		}
	}
	nb_ocodecs++;
	return true;
}

bool wxFfmpegMediaTranscoder::AddAudioStream(AVFormatContext *oc, AudioFormat audioFormat, int bitrate) {
	AVStream *st;
	AVCodecContext *audio_enc;

	CodecID codecId = CODEC_ID_NONE;
	if (audioFormat != afNONE)
		codecId = audioFormat == afMP2 ? CODEC_ID_MP2 : CODEC_ID_AC3;
	audio_sample_rate = 48000;

	st = av_new_stream(oc, oc->nb_streams);
	if (!st) {
		wxLogError(wxT("Could not alloc stream"));
		return false;
	}
	avcodec_get_context_defaults2(st->codec, CODEC_TYPE_AUDIO);

	bitstream_filters[nb_output_files][oc->nb_streams - 1]
			= audio_bitstream_filters;
	audio_bitstream_filters= NULL;

	if (thread_count>1)
		avcodec_thread_init(st->codec, thread_count);

	audio_enc = st->codec;
	audio_enc->codec_type = CODEC_TYPE_AUDIO;

	if (audio_codec_tag)
		audio_enc->codec_tag= audio_codec_tag;

	if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
		audio_enc->flags |= CODEC_FLAG_GLOBAL_HEADER;
	}
	if (audioFormat == afCOPY) {
		st->stream_copy = 1;
		audio_enc->channels = audio_channels;
	} else {
		av_set_int(audio_enc, "ab", bitrate*1000);
		AVCodec *codec = avcodec_find_encoder(codecId);
		output_codecs[nb_ocodecs] = codec;
		audio_enc->codec_id = codecId;

		if (audio_qscale > QSCALE_NONE) {
			audio_enc->flags |= CODEC_FLAG_QSCALE;
			st->quality = FF_QP2LAMBDA * audio_qscale;
			audio_enc->global_quality = (int) st->quality;
		}
		audio_enc->thread_count = thread_count;
		audio_enc->channels = audio_channels;
		audio_enc->sample_fmt = audio_sample_fmt;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 5, 0)
		audio_enc->channel_layout = channel_layout;
#endif
		
		if(codec && codec->sample_fmts){
			const SampleFormat *p= codec->sample_fmts;
			for(; *p!=-1; p++){
				if(*p == audio_enc->sample_fmt)
				break;
			}
			if(*p == -1)
				audio_enc->sample_fmt = codec->sample_fmts[0];
        }
    }
    nb_ocodecs++;
	audio_enc->sample_rate = audio_sample_rate;
	audio_enc->time_base= (AVRational) {1, audio_sample_rate};
	if (audio_language) {
		av_metadata_set(&st->metadata, "language", audio_language);
		av_free(audio_language);
		audio_language = NULL;
	}

	/* reset some key parameters */
	return true;
}

bool wxFfmpegMediaTranscoder::AddSubtitleStream(AVFormatContext *oc,
		SubtitleFormat subtitleFormat) {
	AVStream *st;
	AVCodecContext *subtitle_enc;

	CodecID codecId = subtitleFormat != sfNONE ? CODEC_ID_DVD_SUBTITLE : CODEC_ID_NONE;

	st = av_new_stream(oc, oc->nb_streams);
	if (!st) {
		wxLogError(wxT("Could not alloc stream"));
		return false;
	}
	avcodec_get_context_defaults2(st->codec, CODEC_TYPE_SUBTITLE);

	bitstream_filters[nb_output_files][oc->nb_streams - 1]
			= subtitle_bitstream_filters;
	subtitle_bitstream_filters= NULL;

	subtitle_enc = st->codec;
	subtitle_enc->codec_type = CODEC_TYPE_SUBTITLE;
	
	if(subtitle_codec_tag)
		subtitle_enc->codec_tag= subtitle_codec_tag;
	
	if (subtitleFormat == sfCOPY) {
		st->stream_copy = 1;
	} else {
		subtitle_enc->codec_id = codecId;
		output_codecs[nb_ocodecs] = avcodec_find_encoder(codecId);
	}
	nb_ocodecs++;

	if (subtitle_language) {
		av_metadata_set(&st->metadata, "language", subtitle_language);
		av_free(subtitle_language);
		subtitle_language = NULL;
	}

	return false;
}

bool wxFfmpegMediaTranscoder::Run(bool& canceled) {
	int ret = 0, i, j, k, n, nb_istreams = 0, nb_ostreams = 0;
	AVFormatContext *is, *os;
	AVCodecContext *codec, *icodec;
	AVOutputStream *ost, **ost_table= NULL;
	AVInputStream *ist, **ist_table= NULL;
	wxString error;
	int want_sdp = 1;
	int bit_buffer_size = 1024*256;
	uint8_t *bit_buffer= NULL;
	uint8_t no_packet[MAX_FILES]={0};
	int no_packet_count=0;
	
	if (canceled)
		return false;

	AVInputFile* file_table = (AVInputFile*) av_mallocz(nb_input_files
			* sizeof(AVInputFile));
	if (!file_table)
		goto fail;

	/* input stream init */
	j = 0;
	for (i=0; i<nb_input_files; i++) {
		is = input_files[i];
		int nbStreams = 0;
		for (k=0; k<(int)is->nb_streams; k++)
			if (is->streams[k]->discard != AVDISCARD_ALL)
				nbStreams++;
		file_table[i].ist_index = j;
		file_table[i].nb_streams = nbStreams;
		j += nbStreams;
	}
	nb_istreams = j;

	ist_table = (AVInputStream**) av_mallocz(nb_istreams
			* sizeof(AVInputStream *));
	if (!ist_table)
		goto fail;

	for (i=0; i<nb_istreams; i++) {
		ist = (AVInputStream*) av_mallocz(sizeof(AVInputStream));
		if (!ist)
			goto fail;
		ist_table[i] = ist;
	}
	j = 0;
	for (i=0; i<nb_input_files; i++) {
		is = input_files[i];
		for (k=0; k<(int)is->nb_streams; k++) {
			if (is->streams[k]->discard == AVDISCARD_ALL)
				continue;
			ist = ist_table[j++];
			ist->st = is->streams[k];
			ist->file_index = i;
			ist->index = k;
			ist->discard = 1; /* the stream is discarded by default (changed later) */

			if (rate_emu) {
				ist->start = av_gettime();
			}
		}
	}

	/* output stream init */
	nb_ostreams = 0;
	for (i=0; i<nb_output_files; i++) {
		os = output_files[i];
		if (!os->nb_streams) {
            dump_format(output_files[i], i, output_files[i]->filename, 1);
			wxLogError(wxT("Output file #%d does not contain any stream"), i);
			return false;
		}
		nb_ostreams += os->nb_streams;
	}
	if (nb_stream_maps > 0 && nb_stream_maps != nb_ostreams) {
		wxLogError(wxT("Number of stream maps must match number of output streams"));
		return false;
	}

	/* Sanity check the mapping args -- do the input files & streams exist? */
	for (i=0; i<nb_stream_maps; i++) {
		int fi = stream_maps[i].file_index;
		int si = stream_maps[i].stream_index;

		if (fi < 0 || fi > nb_input_files - 1 ||
            si < 0 || si > file_table[fi].nb_streams - 1) {
        	wxLogError(wxT("Could not find input stream #%d.%d"), fi, si);
            return false;
        }
        fi = stream_maps[i].sync_file_index;
		si = stream_maps[i].sync_stream_index;
		if (fi < 0 || fi> nb_input_files - 1 ||
				si < 0 || si> file_table[fi].nb_streams - 1) {
			wxLogError(wxT("Could not find sync stream #%d.%d"), fi, si);
			return false;
		}
	}

	ost_table = (AVOutputStream**) av_mallocz(sizeof(AVOutputStream*) *nb_ostreams);
	if (!ost_table)
		goto fail;
	for (i=0; i<nb_ostreams; i++) {
		ost = (AVOutputStream*) av_mallocz(sizeof(AVOutputStream));
		ost->fifo = NULL;
		if (!ost)
			goto fail;
		ost_table[i] = ost;
	}

	n = 0;
	for (k=0; k<nb_output_files; k++) {
		os = output_files[k];
		for (i=0; i<(int)os->nb_streams; i++,n++) {
			int found;
			ost = ost_table[n];
			ost->file_index = k;
			ost->index = i;
			ost->st = os->streams[i];
			if (nb_stream_maps > 0) {
				ost->source_index = file_table[stream_maps[n].file_index].ist_index +
					stream_maps[n].stream_index;

				/* Sanity check that the stream types match */
				if (ist_table[ost->source_index]->st->codec->codec_type
						!= ost->st->codec->codec_type) {
					wxLogError(wxT("Codec type mismatch for mapping #%d.%d -> #%d.%d"),
						stream_maps[n].file_index, stream_maps[n].stream_index,
						ost->file_index, ost->index);
					return false;
				}

			} else {
//				if(opt_programid) {
//					found = 0;
//					j = stream_index_from_inputs(input_files, nb_input_files, file_table, ist_table, ost->st->codec->codec_type, opt_programid);
//					if(j != -1) {
//						ost->source_index = j;
//						found = 1;
//					}
//				} else {
					/* get corresponding input stream index : we select the first one with the right type */
					found = 0;
					for (j=0; j<nb_istreams; j++) {
						ist = ist_table[j];
						if (ist->discard && ist->st->codec->codec_type == ost->st->codec->codec_type) {
							ost->source_index = j;
							found = 1;
							break;
						}
					}
//				}
//				if (!found) {
//					if(! opt_programid) {
//						/* try again and reuse existing stream */
//						for(j=0;j<nb_istreams;j++) {
//							ist = ist_table[j];
//							if (ist->st->codec->codec_type == ost->st->codec->codec_type) {
//								ost->source_index = j;
//								found = 1;
//							}
//						}
//					}
					if (!found) {
//						int i= ost->file_index;
//						dump_format(output_files[i], i, output_files[i]->filename, 1);
						wxLogError(wxT("Could not find input stream matching output stream #%d.%d"),
							ost->file_index, ost->index);
						return false;
					}
//				}
			}
			ist = ist_table[ost->source_index];
			ist->discard = 0;
			ost->sync_ist = (nb_stream_maps > 0) ?
					ist_table[file_table[stream_maps[n].sync_file_index].ist_index +
					stream_maps[n].sync_stream_index] : ist;
		}
	}

	/* for each output stream, we compute the right encoding parameters */
	for (i=0; i<nb_ostreams; i++) {
		AVMetadataTag *lang;
		ost = ost_table[i];
		os = output_files[ost->file_index];
		ist = ist_table[ost->source_index];

		codec = ost->st->codec;
		icodec = ist->st->codec;

		if ((lang=av_metadata_get(ist->st->metadata, "language", NULL, 0))
				&& !av_metadata_get(ost->st->metadata, "language", NULL, 0))
			av_metadata_set(&ost->st->metadata, "language", lang->value);
		 
		
		ost->st->disposition = ist->st->disposition;

		if (ost->st->stream_copy) {
			/* if stream_copy is selected, no need to decode or encode */
			codec->codec_id = icodec->codec_id;
			codec->codec_type = icodec->codec_type;

			if (!codec->codec_tag) {
				if ( !os->oformat->codec_tag || av_codec_get_id(
						(const struct AVCodecTag **) os->oformat->codec_tag, icodec->codec_tag) > 0
						|| av_codec_get_tag((const struct AVCodecTag **) os->oformat->codec_tag,
								icodec->codec_id) <= 0)
					codec->codec_tag = icodec->codec_tag;
			}

			codec->bit_rate = icodec->bit_rate;
			codec->extradata= icodec->extradata;
			codec->extradata_size= icodec->extradata_size;
			if(av_q2d(icodec->time_base)*icodec->ticks_per_frame > av_q2d(ist->st->time_base) && av_q2d(ist->st->time_base) < 1.0/1000){
				codec->time_base = icodec->time_base;
				codec->time_base.num *= icodec->ticks_per_frame;
			} else
				codec->time_base = ist->st->time_base;
			switch (codec->codec_type) {
			case CODEC_TYPE_AUDIO:
				if(audio_volume != 256) {
					wxLogError(wxT("-acodec copy and -vol are incompatible (frames are not decoded)"));
					return false;
				}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 5, 0)
				codec->channel_layout = icodec->channel_layout;
#endif
				codec->sample_rate = icodec->sample_rate;
				codec->channels = icodec->channels;
				codec->frame_size = icodec->frame_size;
				codec->block_align= icodec->block_align;
				if (codec->block_align == 1 && codec->codec_id == CODEC_ID_MP3)
					codec->block_align= 0;
				if (codec->codec_id == CODEC_ID_AC3)
					codec->block_align= 0;
				break;
			case CODEC_TYPE_VIDEO:
				codec->pix_fmt = icodec->pix_fmt;
				codec->width = icodec->width;
				codec->height = icodec->height;
				codec->has_b_frames = icodec->has_b_frames;
				break;
			case CODEC_TYPE_SUBTITLE:
				codec->width = icodec->width;
				codec->height = icodec->height;
				break;
			default:
				wxLogError(_("Unknown codec type"));
				return false;
			}
		} else {
			switch (codec->codec_type) {
			case CODEC_TYPE_AUDIO:
#if LIBAVUTIL_VERSION_INT > AV_VERSION_INT(49, 15, 0)
				ost->fifo= av_fifo_alloc(1024);
				if(!ost->fifo)
					goto fail;
#else
				ost->fifo = (AVFifoBuffer*) av_mallocz(sizeof(AVFifoBuffer));
				if (!ost->fifo || av_fifo_init(ost->fifo, 1024))
					goto fail;
#endif
				ost->reformat_pair = MAKE_SFMT_PAIR(SAMPLE_FMT_NONE,SAMPLE_FMT_NONE);
				ost->audio_resample = codec->sample_rate != icodec->sample_rate || audio_sync_method > 1;
				icodec->request_channels = codec->channels;
				ist->decoding_needed = 1;
				ost->encoding_needed = 1;
				break;
			case CODEC_TYPE_VIDEO:
				ost->video_crop = ((frame_leftBand + frame_rightBand
						+ frame_topBand + frame_bottomBand) != 0);
				ost->video_pad = ((frame_padleft + frame_padright
						+ frame_padtop + frame_padbottom) != 0);
				ost->video_resample = ((codec->width != icodec->width
						- (frame_leftBand + frame_rightBand) + (frame_padleft
						+ frame_padright)) || (codec->height != icodec->height
						- (frame_topBand + frame_bottomBand) + (frame_padtop
						+ frame_padbottom)) || (codec->pix_fmt
						!= icodec->pix_fmt));
				if (ost->video_crop) {
					ost->topBand = frame_topBand;
					ost->leftBand = frame_leftBand;
				}
				if (ost->video_pad) {
					ost->padtop = frame_padtop;
					ost->padleft = frame_padleft;
					ost->padbottom = frame_padbottom;
					ost->padright = frame_padright;
					if (!ost->video_resample) {
						avcodec_get_frame_defaults(&ost->pict_tmp);
						if (avpicture_alloc( (AVPicture*)&ost->pict_tmp,
								codec->pix_fmt, codec->width, codec->height) )
							goto fail;
					}
				}
				if (ost->video_resample) {
					avcodec_get_frame_defaults(&ost->pict_tmp);
					if (avpicture_alloc( (AVPicture*)&ost->pict_tmp,
							codec->pix_fmt, codec->width, codec->height) ) {
						wxLogError(wxT("Cannot allocate temp picture, check pix fmt"));
						return false;
					}
					ost->img_resample_ctx
							= sws_getContext(icodec->width - (frame_leftBand
									+ frame_rightBand), icodec->height
									- (frame_topBand + frame_bottomBand),
									icodec->pix_fmt, codec->width
											- (frame_padleft + frame_padright),
									codec->height - (frame_padtop
											+ frame_padbottom), codec->pix_fmt,
									sws_flags, NULL, NULL, NULL);
					if (ost->img_resample_ctx == NULL) {
						wxLogError(wxT("Cannot get resampling context"));
						return false;
					}
					ost->resample_height = icodec->height - (frame_topBand
							+ frame_bottomBand);
				}
				ost->encoding_needed = 1;
				ist->decoding_needed = 1;
				break;
			case CODEC_TYPE_SUBTITLE:
				ost->encoding_needed = 1;
				ist->decoding_needed = 1;
				break;
			default:
				wxLogError(_("Unknown codec type"));
				return false;
			}
			/* two pass mode */
			if (ost->encoding_needed && (codec->flags
					& (CODEC_FLAG_PASS1 | CODEC_FLAG_PASS2))) {
				char logfilename[1024];
				FILE *f;
				int size;
				char *logbuffer;

				snprintf(logfilename, sizeof(logfilename), "%s-%d.log", pass_logfilename_prefix, i);
				if (codec->flags & CODEC_FLAG_PASS1) {
					f = fopen(logfilename, "w");
					if (!f) {
						wxLogError(wxT("Cannot write log file '%s' for pass-1 encoding: %s"), logfilename, strerror(errno));
						return false;
					}
					ost->logfile = f;
				} else {
					/* read the log file */
					f = fopen(logfilename, "r");
					if (!f) {
						wxLogError(wxT("Cannot read log file '%s' for pass-2 encoding: %s\n"), logfilename, strerror(errno));
						return false;
					}
					fseek(f, 0, SEEK_END);
					size = ftell(f);
					fseek(f, 0, SEEK_SET);
					logbuffer = (char*) av_malloc(size + 1);
					if (!logbuffer) {
						wxLogError(wxT("Could not allocate log buffer"));
						return false;
					}
					size = fread(logbuffer, 1, size, f);
					fclose(f);
					logbuffer[size] = '\0';
					codec->stats_in = logbuffer;
				}
			}
		}
		if (codec->codec_type == CODEC_TYPE_VIDEO) {
			int size= codec->width * codec->height;
			bit_buffer_size= FFMAX(bit_buffer_size, 6*size + 200);
		}
	}

	if (!bit_buffer)
		bit_buffer = (uint8_t*) av_malloc(bit_buffer_size);
	if (!bit_buffer) {
		goto fail;
	}

	/* open each encoder */
	for (i=0; i<nb_ostreams; i++) {
		ost = ost_table[i];
		if (ost->encoding_needed) {
			AVCodec *codec = output_codecs[i];
			if (!codec)
				codec = avcodec_find_encoder(ost->st->codec->codec_id);
			if (!codec) {
				error = wxString::Format(wxT("Unsupported codec for output stream #%d.%d"),
						ost->file_index, ost->index);
				ret = AVERROR(EINVAL);
				goto dump_format;
			}
			if (avcodec_open(ost->st->codec, codec) < 0) {
				error = wxString::Format(wxT("Error while opening codec for output stream #%d.%d - maybe incorrect parameters such as bit_rate, rate, width or height"),
						ost->file_index, ost->index);
				ret = AVERROR(EINVAL);
				goto dump_format;
			}
			extra_size += ost->st->codec->extradata_size;
		}
	}

	/* open each decoder */
	for (i=0; i<nb_istreams; i++) {
		ist = ist_table[i];
		if (ist->decoding_needed) {
			AVCodec *codec = avcodec_find_decoder(ist->st->codec->codec_id);
			if (!codec) {
				wxLogError(wxT("Unsupported codec (id=%d) for input stream #%d.%d"),
				ist->st->codec->codec_id, ist->file_index, ist->index);
				return false;
			}
			if (avcodec_open(ist->st->codec, codec) < 0) {
				wxLogError(wxT("Error while opening codec for input stream #%d.%d"),
				ist->file_index, ist->index);
				return false;
			}
			//if (ist->st->codec->codec_type == CODEC_TYPE_VIDEO)
			//    ist->st->codec->flags |= CODEC_FLAG_REPEAT_FIELD;
		}
	}

	/* init pts */
	for (i=0; i<nb_istreams; i++) {
		ist = ist_table[i];
		is = input_files[ist->file_index];
		ist->pts = 0;
		ist->next_pts= AV_NOPTS_VALUE;
		ist->is_start = 1;
	}

	/* set meta data information from input file if required */
	for (i=0; i<nb_meta_data_maps; i++) {
		AVMetadataTag *mtag;
		AVFormatContext *out_file;
		AVFormatContext *in_file;

		int out_file_index = meta_data_maps[i].out_file;
		int in_file_index = meta_data_maps[i].in_file;
		if (out_file_index < 0 || out_file_index >= nb_output_files) {
			error = wxString::Format(wxT("Invalid output file index %d map_meta_data(%d,%d)"),
					out_file_index, out_file_index, in_file_index);
			ret = AVERROR(EINVAL);
			goto dump_format;
		}
		if (in_file_index < 0 || in_file_index >= nb_input_files) {
			error = wxString::Format(wxT("Invalid input file index %d map_meta_data(%d,%d)"),
					in_file_index, out_file_index, in_file_index);
			ret = AVERROR(EINVAL);
			goto dump_format;
		}

		out_file = output_files[out_file_index];
		in_file = input_files[in_file_index];
		
		mtag=NULL;
		while((mtag=av_metadata_get(in_file->metadata, "", mtag, AV_METADATA_IGNORE_SUFFIX)))
			av_metadata_set(&out_file->metadata, mtag->key, mtag->value);
		av_metadata_conv(out_file, out_file->oformat->metadata_conv, in_file->iformat->metadata_conv);
	}

	/* open files and write file headers */
	for (i=0; i<nb_output_files; i++) {
		os = output_files[i];
		if (av_write_header(os) < 0) {
			wxLogError(wxT("Could not write header for output file #%d (incorrect codec parameters ?)"), i);
			ret = AVERROR(EINVAL);
			goto dump_format;
		}
		if (strcmp(output_files[i]->oformat->name, "rtp")) {
			want_sdp = 0;
		}
	}
	
dump_format:
	/* dump the file output parameters - cannot be done before in case
	   of stream copy */
	for(i=0;i<nb_output_files;i++) {
	    dump_format(output_files[i], i, output_files[i]->filename, 1);
	}

	/* dump the stream mapping */
	if (verbose >= 0) {
	    wxLogMessage(wxT("Stream mapping:"));
	    for(i=0;i<nb_ostreams;i++) {
	        ost = ost_table[i];
	        wxString msg = wxString::Format(wxT("  Stream #%d.%d -> #%d.%d"),
	                ist_table[ost->source_index]->file_index, ist_table[ost->source_index]->index,
	                ost->file_index, ost->index);
	        if (ost->sync_ist != ist_table[ost->source_index])
	        	msg += wxString::Format(wxT(" [sync #%d.%d]"), ost->sync_ist->file_index, ost->sync_ist->index);
	        wxLogError(msg);
	    }
	}
	if (ret) {
		wxLogError(error);
	    goto fail;
	}
	
//    if (want_sdp) {
//        print_sdp(output_files, nb_output_files);
//    }

	timer_start = av_gettime();

	while (true) { //for(; received_sigterm == 0;) {
		int file_index, ist_index;
		AVPacket pkt;
		double ipts_min;
		double opts_min;

	redo:
		ipts_min= 1e100;
		opts_min= 1e100;
		
		// check if user canceled the transcoding
		if (canceled)
			return false;

		/* select the stream that we must read now by looking at the
		 smallest output pts */
		file_index = -1;
		for (i=0; i<nb_ostreams; i++) {
			double ipts, opts;
			ost = ost_table[i];
			os = output_files[ost->file_index];
			ist = ist_table[ost->source_index];
			if(no_packet[ist->file_index])
				continue;
			if (ost->st->codec->codec_type == CODEC_TYPE_VIDEO)
				opts = ost->sync_opts * av_q2d(ost->st->codec->time_base);
			else
				opts = ost->st->pts.val * av_q2d(ost->st->time_base);
			ipts = (double)ist->pts;
			if (!file_table[ist->file_index].eof_reached) {
				if (ipts < ipts_min) {
					ipts_min = ipts;
					if (input_sync)
						file_index = ist->file_index;
				}
				if (opts < opts_min) {
					opts_min = opts;
					if (!input_sync)
						file_index = ist->file_index;
				}
			}
			if (ost->frame_number >= max_frames[ost->st->codec->codec_type]) {
				file_index= -1;
				break;
			}
		}
		/* if none, if is finished */
		if (file_index < 0) {
			if(no_packet_count){
				no_packet_count=0;
				memset(no_packet, 0, sizeof(no_packet));
				wxMicroSleep(10000);
				continue;
			}
			break;
		}

		/* finish if recording time exhausted */
		//if (opts_min >= (recording_time / 1000000.0))
			//break;

		/* finish if limit size exhausted */
		//		if (limit_filesize != 0 && limit_filesize < url_ftell(output_files[0]->pb))
		//			break;

		/* read a frame from it and output it in the fifo */
		is = input_files[file_index];
		ret= av_read_frame(is, &pkt);
		if(ret == AVERROR(EAGAIN)){
			no_packet[file_index]=1;
			no_packet_count++;
			continue;
		}
		if (ret < 0) {
			file_table[file_index].eof_reached = 1;
			if (opt_shortest)
				break;
			else
				continue;
		}
		
		no_packet_count=0;
		memset(no_packet, 0, sizeof(no_packet));
		
//		if (do_pkt_dump) {
//			av_pkt_dump_log(NULL, AV_LOG_DEBUG, &pkt, do_hex_dump);
//		}
		
		/* the following test is needed in case new streams appear
		 dynamically in stream : we ignore them */
		if (pkt.stream_index >= file_table[file_index].nb_streams)
			goto discard_packet;
		ist_index = file_table[file_index].ist_index + pkt.stream_index;
		ist = ist_table[ist_index];
		if (ist->discard)
			goto discard_packet;
		
		if (pkt.dts != (int64_t) AV_NOPTS_VALUE)
			pkt.dts += av_rescale_q(input_files_ts_offset[ist->file_index], AV_TIME_BASE_Q, ist->st->time_base);
		if (pkt.pts != (int64_t) AV_NOPTS_VALUE)
			pkt.pts += av_rescale_q(input_files_ts_offset[ist->file_index], AV_TIME_BASE_Q, ist->st->time_base);
		
//		if(input_files_ts_scale[file_index][pkt.stream_index]){
//			if(pkt.pts != (int64_t) AV_NOPTS_VALUE)
//				pkt.pts = (int64_t) (pkt.pts*input_files_ts_scale[file_index][pkt.stream_index]);
//			if(pkt.dts != (int64_t) AV_NOPTS_VALUE)
//				pkt.dts = (int64_t) (pkt.dts*input_files_ts_scale[file_index][pkt.stream_index]);
//		}
		
		if (pkt.dts != (int64_t) AV_NOPTS_VALUE && ist->next_pts != (int64_t) AV_NOPTS_VALUE
				&& (is->iformat->flags & AVFMT_TS_DISCONT)) {
			int64_t pkt_dts= av_rescale_q(pkt.dts, ist->st->time_base, AV_TIME_BASE_Q);
			int64_t delta= pkt_dts - ist->next_pts;
			if ((FFABS(delta)> 1LL*dts_delta_threshold*AV_TIME_BASE || pkt_dts+1<ist->pts)
					&& !copy_ts) {
				input_files_ts_offset[ist->file_index]-= delta;
//				if (verbose > 2)
//					wxLogMessage(wxT("timestamp discontinuity %"PRId64", new offset= %"PRId64""), delta, input_files_ts_offset[ist->file_index]);
				pkt.dts-= av_rescale_q(delta, AV_TIME_BASE_Q , ist->st->time_base);
				if (pkt.pts != (int64_t) AV_NOPTS_VALUE)
					pkt.pts-= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base);
			}
		}
		
		//wxLogMessage(wxT("read #%d.%d size=%d"), ist->file_index, ist->index, pkt.size);
		if (!OutputPacket(ist, ist_index, ost_table, nb_ostreams, &pkt, bit_buffer_size, bit_buffer)) {
			if (verbose >= 0)
				wxLogMessage(wxT("Error while decoding stream #%d.%d"), ist->file_index, ist->index);
			if (exit_on_error)
				return false;
			av_free_packet(&pkt);
			goto redo;
		}
		
	discard_packet:
		av_free_packet(&pkt);
		
		/* dump report by using the output first video and audio streams */
		PrintReport(output_files, ost_table, nb_ostreams, 0);
	}
	
	/* at the end of stream, we must flush the decoder buffers */
	for (i=0; i<nb_istreams; i++) {
		ist = ist_table[i];
		if (ist->decoding_needed)
			OutputPacket(ist, i, ost_table, nb_ostreams, NULL, bit_buffer_size, bit_buffer);
	}

	/* write the trailer if needed and close file */
	for (i=0; i<nb_output_files; i++) {
		os = output_files[i];
		av_write_trailer(os);
	}
	
	/* dump report by using the first video and audio streams */
	PrintReport(output_files, ost_table, nb_ostreams, 1);
	
	/* close each encoder */
	for (i=0; i<nb_ostreams; i++) {
		ost = ost_table[i];
		if (ost->encoding_needed) {
			av_freep(&ost->st->codec->stats_in);
			avcodec_close(ost->st->codec);
		}
	}
	
	/* close each decoder */
	for (i=0; i<nb_istreams; i++) {
		ist = ist_table[i];
		if (ist->decoding_needed) {
			avcodec_close(ist->st->codec);
		}
	}
	
	/* finished ! */
	ret = 0;
fail:
	av_freep(&bit_buffer);
	av_free(file_table);
	
	if (ist_table) {
		for (i=0; i<nb_istreams; i++) {
			ist = ist_table[i];
			av_free(ist);
		}
		av_free(ist_table);
	}
	if (ost_table) {
		for (i=0; i<nb_ostreams; i++) {
			ost = ost_table[i];
			if (ost) {
				if (ost->logfile) {
					fclose(ost->logfile);
					ost->logfile = NULL;
				}
#if LIBAVUTIL_VERSION_INT > AV_VERSION_INT(49, 15, 0)
				av_fifo_free(ost->fifo); /* works even if fifo is not initialized but set to zero */
#else
				if (ost->fifo)
					av_free(ost->fifo);
#endif
				av_free(ost->pict_tmp.data[0]);
				if (ost->video_resample)
					sws_freeContext(ost->img_resample_ctx);
				if (ost->resample)
					audio_resample_close(ost->resample);
				//if (ost->reformat_ctx)
				//	av_audio_convert_free(ost->reformat_ctx);
				av_free(ost);
			}
		}
		av_free(ost_table);
	}
	if (ret == 0)
		End();
	return ret == 0;
}

/* pkt = NULL means EOF (needed to flush decoder buffers) */
bool wxFfmpegMediaTranscoder::OutputPacket(AVInputStream *ist,
		int ist_index, AVOutputStream **ost_table,
		int nb_ostreams, const AVPacket *pkt,
		int bit_buffer_size, uint8_t* bit_buffer) {
	AVFormatContext *os;
	AVOutputStream *ost;
	uint8_t *ptr;
	int len, ret, i;
	uint8_t *data_buf;
	int data_size, got_picture;
	AVFrame picture;
	void *buffer_to_free;
	static unsigned int samples_size= 0;
	static short *samples= NULL;
	AVSubtitle subtitle, *subtitle_to_free;
	int got_subtitle;
	
	if(ist->next_pts == (int64_t) AV_NOPTS_VALUE)
		ist->next_pts= ist->pts;

	if (pkt == NULL) {
		/* EOF handling */
		ptr = NULL;
		len = 0;
		goto handle_eof;
	}
	
	if(pkt->dts != (int64_t) AV_NOPTS_VALUE)
		ist->next_pts = ist->pts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q);
	
	len = pkt->size;
	ptr = pkt->data;
	//while we have more to decode or while the decoder did output something on EOF
	while (len > 0 || (!pkt && ist->next_pts != ist->pts)) {
	handle_eof:
		ist->pts= ist->next_pts;
		
		if(len && len != pkt->size && verbose>0)
			wxLogError(wxT("Multiple frames in a packet from stream %d"), pkt->stream_index);
		/* decode the packet if needed */
		data_buf = NULL; /* fail safe */
		data_size = 0;
		subtitle_to_free = NULL;
		if (ist->decoding_needed) {
			switch (ist->st->codec->codec_type) {
			case CODEC_TYPE_AUDIO: {
				if(pkt && samples_size < FFMAX(pkt->size*sizeof(*samples), AVCODEC_MAX_AUDIO_FRAME_SIZE)) {
					samples_size = FFMAX(pkt->size*sizeof(*samples), AVCODEC_MAX_AUDIO_FRAME_SIZE);
					av_free(samples);
					samples= (short *) av_malloc(samples_size);
                }
				data_size= samples_size;
				/* XXX: could avoid copy if PCM 16 bits with same
				 endianness as CPU */
				ret = avcodec_decode_audio2(ist->st->codec,
						samples, &data_size, ptr, len);
				if (ret < 0)
					goto fail_decode;
				ptr += ret;
				len -= ret;
				/* Some bug in mpeg audio decoder gives */
				/* data_size < 0, it seems they are overflows */
				if (data_size <= 0) {
					/* no audio frame */
					continue;
				}
				data_buf = (uint8_t *)samples;
				ist->next_pts += ((int64_t)AV_TIME_BASE/2
						* data_size)
						/ (ist->st->codec->sample_rate
								* ist->st->codec->channels);
				break;
			}
			case CODEC_TYPE_VIDEO:
				data_size = (ist->st->codec->width
						* ist->st->codec->height * 3) / 2;
				/* XXX: allocate picture correctly */
				avcodec_get_frame_defaults(&picture);
				
				ret = avcodec_decode_video(ist->st->codec,
						&picture, &got_picture, ptr, len);
				ist->st->quality= picture.quality;
				if (ret < 0)
					goto fail_decode;
				if (!got_picture) {
					/* no picture yet */
					goto discard_packet;
				}
				if (ist->st->codec->time_base.num != 0) {
					int ticks= ist->st->parser ? ist->st->parser->repeat_pict+1 : ist->st->codec->ticks_per_frame;
					ist->next_pts += ((int64_t)AV_TIME_BASE
							* ist->st->codec->time_base.num * ticks)
							/ ist->st->codec->time_base.den;
				}
				len = 0;
				break;
			case CODEC_TYPE_SUBTITLE:
				ret = avcodec_decode_subtitle(ist->st->codec,
						&subtitle, &got_subtitle, ptr, len);
				if (ret < 0)
					goto fail_decode;
				if (!got_subtitle) {
					goto discard_packet;
				}
				subtitle_to_free = &subtitle;
				len = 0;
				break;
			default:
				goto fail_decode;
			}
		} else {
			switch (ist->st->codec->codec_type) {
			case CODEC_TYPE_AUDIO:
				ist->next_pts += ((int64_t)AV_TIME_BASE
						* ist->st->codec->frame_size)
						/ ist->st->codec->sample_rate;
				break;
			case CODEC_TYPE_VIDEO:
				if (ist->st->codec->time_base.num != 0) {
					int ticks= ist->st->parser ? ist->st->parser->repeat_pict+1 : ist->st->codec->ticks_per_frame;
					ist->next_pts += ((int64_t)AV_TIME_BASE
							* ist->st->codec->time_base.num * ticks)
							/ ist->st->codec->time_base.den;
				}
				break;
			default:
				break;
			}
			data_buf = ptr;
			data_size = len;
			ret = len;
			len = 0;
		}
		
		buffer_to_free = NULL;
//		if (ist->st->codec->codec_type == CODEC_TYPE_VIDEO) {
//			pre_process_video_frame(ist, (AVPicture *)&picture, &buffer_to_free);
//		}
		
		// preprocess audio (volume)
		if (ist->st->codec->codec_type == CODEC_TYPE_AUDIO) {
			if (audio_volume != 256) {
				short *volp;
				volp = samples;
				for (i=0; i<(int)(data_size / sizeof(short)); i++) {
					int v = ((*volp) * audio_volume + 128) >> 8;
					if (v < -32768)
						v = -32768;
					if (v > 32767)
						v = 32767;
					*volp++ = v;
				}
			}
		}
		
		/* frame rate emulation */
		if (ist->st->codec->rate_emu) {
			int64_t pts = av_rescale((int64_t) ist->frame * ist->st->codec->time_base.num, 1000000, ist->st->codec->time_base.den);
			int64_t now = av_gettime() - ist->start;
			if (pts > now)
				wxMicroSleep(pts - now);
			
			ist->frame++;
		}
		
		/* if output time reached then transcode raw format,
		 encode packets and output them */
		if (start_time == 0 || ist->pts >= start_time) {
			for (i=0; i<nb_ostreams; i++) {
				int frame_size;
				
				ost = ost_table[i];
				if (ost->source_index == ist_index) {
					os = output_files[ost->file_index];
					
					/* set the input output pts pairs */
					//ost->sync_ipts = (double)(ist->pts + input_files_ts_offset[ist->file_index] - start_time)/ AV_TIME_BASE;
					
					if (ost->encoding_needed) {
						switch (ost->st->codec->codec_type) {
						case CODEC_TYPE_AUDIO:
							if (!DoAudioOut(os, ost, ist,
									data_buf, data_size))
								return false;
							break;
						case CODEC_TYPE_VIDEO:
							if (!DoVideoOut(os, ost, ist,
									&picture, &frame_size,
									bit_buffer_size, bit_buffer))
								return false;
							//                            if (vstats_filename && frame_size)
							//                                do_video_stats(os, ost, frame_size);
							break;
						case CODEC_TYPE_SUBTITLE:
							if (!DoSubtitleOut(os, ost, ist,
									&subtitle, pkt->pts))
								return false;
							break;
						default:
							wxLogError(_("Unknown codec type"));
							return false;
						}
					} else {
						AVFrame avframe; //FIXME/XXX remove this
						AVPacket opkt;
						av_init_packet(&opkt);
						
						if ((!ost->frame_number && !(pkt->flags & PKT_FLAG_KEY)) && !copy_initial_nonkeyframes)
							continue;
						
						/* no reencoding needed : output the packet directly */
						/* force the input stream PTS */
						
						avcodec_get_frame_defaults(&avframe);
						ost->st->codec->coded_frame= &avframe;
						avframe.key_frame = pkt->flags & PKT_FLAG_KEY;
						
						if (ost->st->codec->codec_type == CODEC_TYPE_AUDIO)
							audio_size += data_size;
						else if (ost->st->codec->codec_type == CODEC_TYPE_VIDEO) {
							video_size += data_size;
							ost->sync_opts++;
						}
						
						opkt.stream_index= ost->index;
						if (pkt->pts != (int64_t) AV_NOPTS_VALUE)
							opkt.pts = av_rescale_q(pkt->pts, ist->st->time_base, ost->st->time_base);
						else
							opkt.pts= AV_NOPTS_VALUE;
						
						if (pkt->dts == (int64_t) AV_NOPTS_VALUE)
							opkt.dts = av_rescale_q(ist->pts, AV_TIME_BASE_Q, ost->st->time_base);
						else
							opkt.dts = av_rescale_q(pkt->dts, ist->st->time_base, ost->st->time_base);
						
						opkt.duration = av_rescale_q(pkt->duration, ist->st->time_base, ost->st->time_base);
						opkt.flags = pkt->flags;
						
						//FIXME remove the following 2 lines they shall be replaced by the bitstream filters
						if (av_parser_change(ist->st->parser, ost->st->codec,
								&opkt.data, &opkt.size, data_buf, data_size,
								pkt->flags & PKT_FLAG_KEY))
							opkt.destruct = av_destruct_packet;
						
						if (!WriteFrame(os, &opkt, ost->st->codec,
								bitstream_filters[ost->file_index][opkt.stream_index]))
							return false;
						
						ost->st->codec->frame_number++;
						ost->frame_number++;
						av_free_packet(&opkt);
					}
				}
			}
		}
		av_free(buffer_to_free);
		/* XXX: allocate the subtitles in the codec ? */
		if (subtitle_to_free) {
			if (subtitle_to_free->rects != NULL) {
				for (i = 0; i < (int) subtitle_to_free->num_rects; i++) {
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 10, 0)
					av_freep(&subtitle_to_free->rects[i]->pict.data[0]);
					av_freep(&subtitle_to_free->rects[i]->pict.data[1]);
					av_freep(&subtitle_to_free->rects[i]);
#else
					av_free(subtitle_to_free->rects[i].bitmap);
					av_free(subtitle_to_free->rects[i].rgba_palette);
#endif
				}
				av_freep(&subtitle_to_free->rects);
			}
			subtitle_to_free->num_rects = 0;
			subtitle_to_free = NULL;
		}
	}
discard_packet:
	if (pkt == NULL) {
		/* EOF handling */
		
		for (i=0; i<nb_ostreams; i++) {
			ost = ost_table[i];
			if (ost->source_index == ist_index) {
				AVCodecContext *enc= ost->st->codec;
				os = output_files[ost->file_index];
				
				if (ost->st->codec->codec_type == CODEC_TYPE_AUDIO
						&& enc->frame_size <=1)
					continue;
				if (ost->st->codec->codec_type == CODEC_TYPE_VIDEO
						&& (os->oformat->flags & AVFMT_RAWPICTURE))
					continue;
				
				if (ost->encoding_needed) {
					for (;;) {
						AVPacket pkt;
						int fifo_bytes;
						av_init_packet(&pkt);
						pkt.stream_index= ost->index;

						switch (ost->st->codec->codec_type) {
						case CODEC_TYPE_AUDIO:
							fifo_bytes = av_fifo_size(ost->fifo);
							ret = 0;
							/* encode any samples remaining in fifo */
							if (fifo_bytes > 0 && enc->codec->capabilities & CODEC_CAP_SMALL_LAST_FRAME) {
								int fs_tmp = enc->frame_size;
								enc->frame_size = fifo_bytes / (2 * enc->channels);
#if LIBAVUTIL_VERSION_INT > AV_VERSION_INT(49, 15, 0)
								av_fifo_generic_read(ost->fifo, samples, fifo_bytes, NULL);
#else
								av_fifo_generic_read(ost->fifo, fifo_bytes, NULL, samples);
#endif
								ret	= avcodec_encode_audio(enc, bit_buffer, bit_buffer_size, samples);
								enc->frame_size = fs_tmp;
							}
							if (ret <= 0) {
								ret = avcodec_encode_audio(enc, bit_buffer, bit_buffer_size, NULL);
							}
							if (ret < 0) {
								wxLogError(wxT("Audio encoding failed"));
								return false;
							}
							audio_size += ret;
							pkt.flags |= PKT_FLAG_KEY;
							break;
						case CODEC_TYPE_VIDEO:
							ret = avcodec_encode_video(enc, bit_buffer, bit_buffer_size, NULL);
							if (ret < 0) {
								wxLogError(wxT("Video encoding failed"));
								return false;
							}
							video_size += ret;
							if (enc->coded_frame && enc->coded_frame->key_frame)
								pkt.flags |= PKT_FLAG_KEY;
							if (ost->logfile && enc->stats_out) {
								fprintf(ost->logfile, "%s", enc->stats_out);
							}
							break;
						default:
							ret=-1;
						}

						if (ret<=0)
							break;
						pkt.data = bit_buffer;
						pkt.size = ret;
						if (enc->coded_frame && enc->coded_frame->pts != (int64_t) AV_NOPTS_VALUE)
							pkt.pts = av_rescale_q(enc->coded_frame->pts, enc->time_base, ost->st->time_base);
						if (!WriteFrame(os, &pkt, ost->st->codec,
								bitstream_filters[ost->file_index][pkt.stream_index]))
							return false;
					}
				}
			}
		}
	}
	return true;
fail_decode:
	return false;
}

double get_sync_ipts(const AVOutputStream *ost, int64_t start_time) {
	const AVInputStream *ist = ost->sync_ist;
	return (double)(ist->pts - start_time)/AV_TIME_BASE;
}

bool wxFfmpegMediaTranscoder::WriteFrame(AVFormatContext *s, AVPacket *pkt,
		AVCodecContext *avctx, AVBitStreamFilterContext *bsfc) {
	int ret;
	
	while (bsfc) {
		AVPacket new_pkt= *pkt;
		int a= av_bitstream_filter_filter(bsfc, avctx,
				NULL, &new_pkt.data, &new_pkt.size,
				pkt->data, pkt->size, pkt->flags
						& PKT_FLAG_KEY);
		if (a>0) {
			av_free_packet(pkt);
			new_pkt.destruct= av_destruct_packet;
		} else if (a<0) {
			wxLogError(wxT("%s failed for stream %d, codec %s"),
			bsfc->filter->name, pkt->stream_index,
			avctx->codec ? avctx->codec->name : "copy");
			PrintError(wxT(""), a);
		}
		*pkt= new_pkt;
		
		bsfc= bsfc->next;
	}
	
	ret = av_interleaved_write_frame(s, pkt);
	if (ret < 0) {
		PrintError(wxT("av_interleaved_write_frame()"), ret);
		return false;
	}
	return true;
}

bool wxFfmpegMediaTranscoder::DoAudioOut(AVFormatContext *s, AVOutputStream *ost,
		AVInputStream *ist, unsigned char *buf, int size) {
	uint8_t *buftmp;
	static uint8_t *audio_buf= NULL;
	static uint8_t *audio_out= NULL;
//	static uint8_t *audio_out2 = NULL;
	const int audio_out_size= 4*MAX_AUDIO_PACKET_SIZE;
	
	int size_out, frame_bytes, ret;
	AVCodecContext *enc= ost->st->codec;
	AVCodecContext *dec= ist->st->codec;
	int osize= av_get_bits_per_sample_format(enc->sample_fmt)/8;
	int isize= av_get_bits_per_sample_format(dec->sample_fmt)/8;
	
	/* SC: dynamic allocation of buffers */
	if (!audio_buf)
		audio_buf = (uint8_t*) av_malloc(2*MAX_AUDIO_PACKET_SIZE);
	if (!audio_out)
		audio_out = (uint8_t*) av_malloc(audio_out_size);
	if (!audio_buf || !audio_out)
		return false; /* Should signal an error ! */
	
	if (enc->channels != dec->channels)
		ost->audio_resample = 1;
	
	if (ost->audio_resample && !ost->resample) {
		if (dec->sample_fmt != SAMPLE_FMT_S16)
			wxLogWarning(wxT("Warning, using s16 intermediate sample format for resampling"));
		ost->resample = av_audio_resample_init(enc->channels, dec->channels,
				enc->sample_rate, dec->sample_rate,
				enc->sample_fmt, dec->sample_fmt,
				16, 10, 0, 0.8);
		if (!ost->resample) {
			wxLogError(wxT("Can not resample %d channels @ %d Hz to %d channels @ %d Hz"),
					dec->channels, dec->sample_rate,
					enc->channels, enc->sample_rate);
			return false;
		}
	}
	
//	if (!ost->audio_resample && dec->sample_fmt!=enc->sample_fmt &&
//		MAKE_SFMT_PAIR(enc->sample_fmt,dec->sample_fmt)!=ost->reformat_pair) {
//		if (!audio_out2)
//			audio_out2 = (uint8_t*) av_malloc(audio_out_size);
//		if (!audio_out2)
//			return false;
//		if (ost->reformat_ctx)
//			av_audio_convert_free(ost->reformat_ctx);
//		ost->reformat_ctx = av_audio_convert_alloc(enc->sample_fmt, 1,
//												   dec->sample_fmt, 1, NULL, 0);
//		if (!ost->reformat_ctx) {
//			wxLogError(wxT("Cannot convert %s sample format to %s sample format"),
//				avcodec_get_sample_fmt_name(dec->sample_fmt),
//				avcodec_get_sample_fmt_name(enc->sample_fmt));
//			return false;
//		}
//		ost->reformat_pair=MAKE_SFMT_PAIR(enc->sample_fmt,dec->sample_fmt);
//	}
	
	if (audio_sync_method) {
		double delta = get_sync_ipts(ost, start_time) * enc->sample_rate - ost->sync_opts
				- av_fifo_size(ost->fifo) / (ost->st->codec->channels * 2);
		double idelta= delta * ist->st->codec->sample_rate / enc->sample_rate;
		int byte_delta= ((int)idelta)*2*ist->st->codec->channels;
		
		//FIXME resample delay
		if (fabs(delta) > 50) {
			if (ist->is_start || fabs(delta)
					> audio_drift_threshold
							*enc->sample_rate) {
				if (byte_delta < 0) {
					byte_delta= FFMAX(byte_delta, -size);
					size += byte_delta;
					buf -= byte_delta;
					if (verbose > 2)
						wxLogMessage(wxT("discarding %d audio samples"), (int)-delta);
					if (!size)
						return true;
					ist->is_start=0;
				} else {
					static uint8_t *input_tmp= NULL;
					input_tmp = (uint8_t*) av_realloc(input_tmp, byte_delta + size);
					
					if (byte_delta + size
							<= MAX_AUDIO_PACKET_SIZE)
						ist->is_start=0;
					else
						byte_delta
								= MAX_AUDIO_PACKET_SIZE - size;
					
					memset(input_tmp, 0, byte_delta);
					memcpy(input_tmp + byte_delta, buf,
							size);
					buf= input_tmp;
					size += byte_delta;
					if (verbose > 2)
						wxLogMessage(wxT("adding %d audio samples of silence"), (int)delta);
				}
			} else if (audio_sync_method>1) {
				int comp= av_clip((int) delta, -audio_sync_method, audio_sync_method);
				assert(ost->audio_resample);
				if (verbose > 2)
					wxLogMessage(wxT("compensating audio timestamp drift:%f compensation:%d in:%d"), delta, comp, enc->sample_rate);
				av_resample_compensate(*(struct AVResampleContext**)ost->resample, comp, enc->sample_rate);
			}
		}
	} else
		ost->sync_opts= (int64_t) rint(get_sync_ipts(ost, start_time) * enc->sample_rate)
				- av_fifo_size(ost->fifo)/(ost->st->codec->channels * 2); //FIXME wrong
	
	if (ost->audio_resample) {
		buftmp = audio_buf;
		size_out = audio_resample(ost->resample,
				(short *)buftmp, (short *)buf,
				size / (ist->st->codec->channels * isize));
		size_out = size_out * enc->channels * osize;
	} else {
		buftmp = buf;
		size_out = size;
	}
	
//	if (!ost->audio_resample && dec->sample_fmt!=enc->sample_fmt) {
//		const void *ibuf[6]= {buftmp};
//		void *obuf[6]= {audio_out2};
//		int istride[6]= {isize};
//		int ostride[6]= {osize};
//		int len= size_out/istride[0];
//		if (av_audio_convert(ost->reformat_ctx, obuf, ostride, ibuf, istride, len)<0) {
//			wxLogError(wxT("av_audio_convert() failed"));
//			if (exit_on_error)
//				return false;
//			return true;
//		}
//		buftmp = audio_out2;
//		size_out = len*osize;
//	}

	/* now encode as many frames as possible */
	if (enc->frame_size > 1) {
		/* output resampled raw samples */
		if (av_fifo_realloc2(ost->fifo, av_fifo_size(ost->fifo) + size_out) < 0) {
            wxLogError(wxT("av_fifo_realloc2() failed"));
            return false;
        }
        av_fifo_generic_write(ost->fifo, buftmp, size_out, NULL);

        frame_bytes = enc->frame_size * osize * enc->channels;

        while (av_fifo_size(ost->fifo) >= frame_bytes) {
			AVPacket pkt;
			av_init_packet(&pkt);
			
#if LIBAVUTIL_VERSION_INT > AV_VERSION_INT(49, 15, 0)
			av_fifo_generic_read(ost->fifo, audio_buf, frame_bytes, NULL);
#else
			av_fifo_generic_read(ost->fifo, frame_bytes, NULL, audio_buf);
#endif

            //FIXME pass ost->sync_opts as AVFrame.pts in avcodec_encode_audio()
			
			ret = avcodec_encode_audio(enc, audio_out, audio_out_size, (short *)audio_buf);
			if (ret < 0) {
				wxLogError(wxT("Audio encoding failed"));
				return false;
			}
			audio_size += ret;
			pkt.stream_index= ost->index;
			pkt.data= audio_out;
			pkt.size= ret;
			if (enc->coded_frame
					&& enc->coded_frame->pts
							!= (int64_t) AV_NOPTS_VALUE)
				pkt.pts= av_rescale_q(
						enc->coded_frame->pts,
						enc->time_base,
						ost->st->time_base);
			pkt.flags |= PKT_FLAG_KEY;
			if (!WriteFrame(
					s,
					&pkt,
					ost->st->codec,
					bitstream_filters[ost->file_index][pkt.stream_index]))
				return false;
			
			ost->sync_opts += enc->frame_size;
		}
	} else {
		AVPacket pkt;
		int coded_bps = av_get_bits_per_sample(enc->codec->id)/8;
		av_init_packet(&pkt);
		
		ost->sync_opts += size_out / (osize * enc->channels);
		
		/* output a pcm frame */
		/* determine the size of the coded buffer */
        size_out /= osize;
        if (coded_bps)
            size_out *= coded_bps;

        //FIXME pass ost->sync_opts as AVFrame.pts in avcodec_encode_audio()
		ret = avcodec_encode_audio(enc, audio_out, size_out, (short *)buftmp);
		if (ret < 0) {
			wxLogError(wxT("Audio encoding failed"));
			return false;
		}
		audio_size += ret;
		pkt.stream_index= ost->index;
		pkt.data= audio_out;
		pkt.size= ret;
		if (enc->coded_frame && enc->coded_frame->pts
				!= (int64_t) AV_NOPTS_VALUE)
			pkt.pts= av_rescale_q(
					enc->coded_frame->pts,
					enc->time_base, ost->st->time_base);
		pkt.flags |= PKT_FLAG_KEY;
		if (!WriteFrame(
				s,
				&pkt,
				ost->st->codec,
				bitstream_filters[ost->file_index][pkt.stream_index]))
			return false;
	}
	return true;
}

bool wxFfmpegMediaTranscoder::DoVideoOut(AVFormatContext *s, AVOutputStream *ost,
		AVInputStream *ist, AVFrame *in_picture, int *frame_size,
		int bit_buffer_size, uint8_t* bit_buffer) {
	int nb_frames, i, ret;
	AVFrame *final_picture, *formatted_picture,
			*resampling_dst, *padding_src;
	AVFrame picture_crop_temp, picture_pad_temp;
	AVCodecContext *enc, *dec;
	
	avcodec_get_frame_defaults(&picture_crop_temp);
	avcodec_get_frame_defaults(&picture_pad_temp);
	
	enc = ost->st->codec;
	dec = ist->st->codec;
	
	/* by default, we output a single frame */
	nb_frames = 1;
	
	*frame_size = 0;
	
	if(video_sync_method>0 || (video_sync_method && av_q2d(enc->time_base) > 0.001)){
		double vdelta;
		vdelta = get_sync_ipts(ost, start_time)
				/ av_q2d(enc->time_base)
				- ost->sync_opts;
		//FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c
		if (vdelta < -1.1)
			nb_frames = 0;
		 else if (video_sync_method == 2 || (video_sync_method < 0 && (s->oformat->flags & AVFMT_VARIABLE_FPS))) {
			if (vdelta <= -0.6) {
				nb_frames = 0;
			} else if (vdelta > 0.6)
			ost->sync_opts= lrintf(get_sync_ipts(ost, start_time) / av_q2d(enc->time_base));
		 } else if (vdelta > 1.1)
			nb_frames = (int) rint(vdelta);
		//wxLogMessage(wxT("vdelta:%f, ost->sync_opts:%"PRId64"), ost->sync_ipts:%f nb_frames:%d\n", vdelta, ost->sync_opts, get_sync_ipts(ost), nb_frames);
		if (nb_frames == 0) {
			++nb_frames_drop;
			if (verbose>2)
				wxLogMessage(wxT("*** drop!"));
		} else if (nb_frames > 1) {
			nb_frames_dup += nb_frames;
			if (verbose>2)
				wxLogMessage(wxT("*** %d dup!"), nb_frames-1);
		}
	} else
		ost->sync_opts = (int64_t) rint(get_sync_ipts(ost, start_time) / av_q2d(enc->time_base));
		
	nb_frames
			= FFMIN(nb_frames, max_frames[CODEC_TYPE_VIDEO] - ost->frame_number);
	if (nb_frames <= 0)
		return true;
	
	if (ost->video_crop) {
		if (av_picture_crop(
				(AVPicture *)&picture_crop_temp,
				(AVPicture *)in_picture, dec->pix_fmt,
				ost->topBand, ost->leftBand) < 0) {
			wxLogError(wxT("error cropping picture"));
			if (exit_on_error)
				return false;
			return true;
		}
		formatted_picture = &picture_crop_temp;
	} else {
		formatted_picture = in_picture;
	}
	
	final_picture = formatted_picture;
	padding_src = formatted_picture;
	resampling_dst = &ost->pict_tmp;
	if (ost->video_pad) {
		final_picture = &ost->pict_tmp;
		if (ost->video_resample) {
			if (av_picture_crop(
					(AVPicture *)&picture_pad_temp,
					(AVPicture *)final_picture,
					enc->pix_fmt, ost->padtop,
					ost->padleft) < 0) {
				wxLogError(wxT("error padding picture"));
				if (exit_on_error)
					return false;
				return true;
			}
			resampling_dst = &picture_pad_temp;
		}
	}
	
	if (ost->video_resample) {
		padding_src = NULL;
		final_picture = &ost->pict_tmp;
		sws_scale(ost->img_resample_ctx,
				formatted_picture->data,
				formatted_picture->linesize, 0,
				ost->resample_height,
				resampling_dst->data,
				resampling_dst->linesize);
	}
	
	if (ost->video_pad) {
		av_picture_pad((AVPicture*)final_picture, (AVPicture *)padding_src, enc->height,
				enc->width, enc->pix_fmt, ost->padtop, ost->padbottom, ost->padleft,
				ost->padright, padcolor);
	}
	
	// duplicates frame if needed
	for (i=0; i<nb_frames; i++) {
		AVPacket pkt;
		av_init_packet(&pkt);
		pkt.stream_index= ost->index;
		
		if (s->oformat->flags & AVFMT_RAWPICTURE) {
			/* raw pictures are written as AVPicture structure to
			 avoid any copies. We support temporarily the older
			 method. */
			AVFrame* old_frame = enc->coded_frame;
			enc->coded_frame = dec->coded_frame; //FIXME/XXX remove this hack
			pkt.data= (uint8_t *)final_picture;
			pkt.size= sizeof(AVPicture);
			pkt.pts= av_rescale_q(ost->sync_opts, enc->time_base, ost->st->time_base);
			pkt.flags |= PKT_FLAG_KEY;
			
			if (!WriteFrame(s, &pkt, ost->st->codec,
					bitstream_filters[ost->file_index][pkt.stream_index]))
				return false;
			enc->coded_frame = old_frame;
		} else {
			AVFrame big_picture;
			
			big_picture= *final_picture;
			// better than nothing: use input picture interlaced settings
			big_picture.interlaced_frame
					= in_picture->interlaced_frame;
			
			// handles sameq here. This is not correct because it may not be a global option
			if (same_quality) {
				big_picture.quality = (int) ist->st->quality;
			} else
				big_picture.quality = (int) ost->st->quality;
			if (!me_threshold)
				big_picture.pict_type = 0;
//            big_picture.pts = AV_NOPTS_VALUE;
			big_picture.pts= ost->sync_opts;
//            big_picture.pts= av_rescale(ost->sync_opts, AV_TIME_BASE*(int64_t)enc->time_base.num, enc->time_base.den);
			ret = avcodec_encode_video(enc, bit_buffer,
					bit_buffer_size, &big_picture);
			if (ret < -1) {
				wxLogError(wxT("Video encoding failed"));
				return false;
			}
			//enc->frame_number = enc->real_pict_num;
			if (ret>0) {
				pkt.data= bit_buffer;
				pkt.size= ret;
				if (enc->coded_frame->pts != (int64_t) AV_NOPTS_VALUE)
					pkt.pts= av_rescale_q(enc->coded_frame->pts, enc->time_base, ost->st->time_base);
				/*av_log(NULL, AV_LOG_DEBUG, "encoder -> %"PRId64"/%"PRId64"\n",
				 pkt.pts != AV_NOPTS_VALUE ? av_rescale(pkt.pts, enc->time_base.den, AV_TIME_BASE*(int64_t)enc->time_base.num) : -1,
				 pkt.dts != AV_NOPTS_VALUE ? av_rescale(pkt.dts, enc->time_base.den, AV_TIME_BASE*(int64_t)enc->time_base.num) : -1);*/
				
				if (enc->coded_frame->key_frame)
					pkt.flags |= PKT_FLAG_KEY;
				if (!WriteFrame(s, &pkt, ost->st->codec,
						bitstream_filters[ost->file_index][pkt.stream_index]))
					return false;
				*frame_size = ret;
				video_size += ret;
				//wxLogMessage(wxT("Frame: %3d %3d size: %5d type: %d"),
				//        enc->frame_number-1, enc->real_pict_num, ret,
				//        enc->pict_type);
				/* if two pass, output log */
				if (ost->logfile && enc->stats_out) {
					fprintf(ost->logfile, "%s",
							enc->stats_out);
				}
			}
		}
		ost->sync_opts++;
		ost->frame_number++;
	}
	return true;
}

bool wxFfmpegMediaTranscoder::DoSubtitleOut(AVFormatContext *s, AVOutputStream *ost,
		AVInputStream *ist, AVSubtitle *sub, int64_t pts) {
	static uint8_t *subtitle_out= NULL;
	int subtitle_out_max_size = 65536;
	int subtitle_out_size, nb, i;
	AVCodecContext *enc;
	AVPacket pkt;
	
	if (pts == (int64_t) AV_NOPTS_VALUE) {
		wxLogError(wxT("Subtitle packets must have a pts"));
		if (exit_on_error)
			return false;
		return true;
	}
	
	enc = ost->st->codec;
	
	if (!subtitle_out) {
		subtitle_out = (uint8_t*) av_malloc(subtitle_out_max_size);
	}
	
	/* Note: DVB subtitle need one packet to draw them and one other
	 packet to clear them */
	/* XXX: signal it in the codec context ? */
	if (enc->codec_id == CODEC_ID_DVB_SUBTITLE)
		nb = 2;
	else
		nb = 1;
	
	for (i = 0; i < nb; i++) {
		subtitle_out_size = avcodec_encode_subtitle(
				enc, subtitle_out,
				subtitle_out_max_size, sub);
		
		av_init_packet(&pkt);
		pkt.stream_index = ost->index;
		pkt.data = subtitle_out;
		pkt.size = subtitle_out_size;
		pkt.pts = av_rescale_q(pts, ist->st->time_base,
				ost->st->time_base);
		if (enc->codec_id == CODEC_ID_DVB_SUBTITLE) {
			/* XXX: the pts correction is handled here. Maybe handling
			 it in the codec would be better */
			if (i == 0)
				pkt.pts += 90 * sub->start_display_time;
			else
				pkt.pts += 90 * sub->end_display_time;
		}
		if (!WriteFrame(s, &pkt, ost->st->codec,
				bitstream_filters[ost->file_index][pkt.stream_index]))
			return false;
	}
	return true;
}

void wxFfmpegMediaTranscoder::PrintReport(AVFormatContext **output_files,
		AVOutputStream **ost_table, int nb_ostreams, int is_last_report) {
	AVOutputStream *ost;
	AVFormatContext *oc, *os;
	int64_t total_size;
	AVCodecContext *enc;
	int frame_number, vid, i;
	double bitrate, ti1, pts;
	static int64_t last_time = -1;
	static int qp_histogram[52];
	
	if (!is_last_report) {
		int64_t cur_time;
		/* display the report every 0.5 seconds */
		cur_time = av_gettime();
		if (last_time == -1) {
			last_time = cur_time;
			return;
		}
		if ((cur_time - last_time) < 500000)
			return;
		last_time = cur_time;
	}
	
	
	oc = output_files[0];
	total_size = url_fsize(oc->pb);
	
	wxString msg;
	ti1 = 1e10;
	vid = 0;
	for (i=0; i<nb_ostreams; i++) {
		ost = ost_table[i];
		os = output_files[ost->file_index];
		enc = ost->st->codec;
		if (vid && enc->codec_type == CODEC_TYPE_VIDEO) {
			msg += wxString::Format(wxT("q=%2.1f "),
					!ost->st->stream_copy ?	enc->coded_frame->quality/(float)FF_QP2LAMBDA : -1);
		}
		if (!vid && enc->codec_type == CODEC_TYPE_VIDEO) {
			float t = (av_gettime()-timer_start) / 1000000.0;
			
			frame_number = ost->frame_number;
			msg += wxString::Format(wxT("frame=%5d fps=%3d q=%3.1f "),
			frame_number, (t>1)?(int)(frame_number/t+0.5) : 0,
					!ost->st->stream_copy ? enc->coded_frame->quality/(float)FF_QP2LAMBDA : -1);
			if (is_last_report)
				msg += wxString::Format(wxT("L"));
			if (qp_hist) {
				int j;
				int qp = (int) rint(enc->coded_frame->quality/(float)FF_QP2LAMBDA);
				if (qp>=0 && qp < (int) (FF_ARRAY_ELEMS(qp_histogram)))
					qp_histogram[qp]++;
				for (j=0; j<32; j++)
					msg += wxString::Format(wxT("%X"), (int)rint(log(qp_histogram[j]+1)/log(2)));
			}
			if (enc->flags&CODEC_FLAG_PSNR) {
				int j;
				double error, error_sum=0;
				double scale, scale_sum=0;
				char type[3]= { 'Y', 'U', 'V' };
				msg += wxString::Format(wxT("PSNR="));
				for (j=0; j<3; j++) {
					if (is_last_report) {
						error= enc->error[j];
						scale= enc->width*enc->height*255.0*255.0*frame_number;
					} else {
						error = enc->coded_frame->error[j];
						scale = enc->width*enc->height *255.0*255.0;
					}
					if (j)
						scale/=4;
					error_sum += error;
					scale_sum += scale;
					msg += wxString::Format(wxT("%c:%2.2f "), type[j], psnr(error/scale));
				}
				msg += wxString::Format(wxT("*:%2.2f "), psnr(error_sum/scale_sum));
			}
			vid = 1;
		}
		/* compute min output value */
		pts = (double)ost->st->pts.val * av_q2d(ost->st->time_base);
		if ((pts < ti1) && (pts > 0))
			ti1 = pts;
	}
	if (ti1 < 0.01)
		ti1 = 0.01;
	
	if (verbose || is_last_report) {
		bitrate = (double)(total_size * 8) / ti1 / 1000.0;
		
		msg += wxString::Format(wxT("size=%8.0fkB time=%0.2f bitrate=%6.1fkbits/s"),
				(double)total_size / 1024, ti1, bitrate);
		
		if (verbose > 1)
			msg += wxString::Format(wxT(" dup=%d drop=%d"), nb_frames_dup, nb_frames_drop);
		
		if (verbose >= 0)
			wxLogMessage(msg);
	}
	
	if (is_last_report && verbose >= 0) {
		int64_t raw= audio_size + video_size + extra_size;
		wxLogMessage(wxT("video:%1.0fkB audio:%1.0fkB global headers:%1.0fkB muxing overhead %f%%"),
				video_size/1024.0,
				audio_size/1024.0,
				extra_size/1024.0,
				100.0*(total_size - raw)/raw);
	}
	wxYieldIfNeeded();
}

void wxFfmpegMediaTranscoder::End() {
	// close files
	for (int i=0; i<nb_output_files; i++) {
		AVFormatContext *s = output_files[i];
		url_fclose(s->pb);
		for (int j=0; j<(int)s->nb_streams; j++) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52, 24, 1)
			av_metadata_free(&s->streams[j]->metadata);
#endif
			av_free(s->streams[j]->codec);
			av_free(s->streams[j]);
		}
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52, 24, 1)
		for(int j=0;j<(int)s->nb_programs;j++) {
			av_metadata_free(&s->programs[j]->metadata);
		}
		for(int j=0;j<(int)s->nb_chapters;j++) {
			av_metadata_free(&s->chapters[j]->metadata);
		}
		av_metadata_free(&s->metadata);
#endif
		av_free(s);
	}
	nb_output_files = 0;
	for (int i=0; i<nb_input_files; i++)
		av_close_input_file(input_files[i]);
	nb_input_files = 0;
	
	av_free(intra_matrix);
	av_free(inter_matrix);
	
	if (vstats_file)
		fclose(vstats_file);
	av_free(vstats_filename);
	
	av_free(video_standard);
}
