2011-01-03 16:51:17 +00:00
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
#include <libavutil/mathematics.h>
|
2011-01-04 14:44:05 +00:00
|
|
|
#include <libavutil/avutil.h>
|
|
|
|
#include <libavutil/avstring.h>
|
2011-01-03 16:51:17 +00:00
|
|
|
#include <libavformat/avformat.h>
|
2011-01-03 19:46:50 +00:00
|
|
|
#include <libswscale/swscale.h>
|
2011-01-03 16:51:17 +00:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include "ffemu.h"
|
|
|
|
|
|
|
|
struct video_info
|
|
|
|
{
|
|
|
|
bool enabled;
|
|
|
|
AVCodecContext *codec;
|
2011-01-03 19:46:50 +00:00
|
|
|
|
|
|
|
AVFrame *conv_frame;
|
|
|
|
uint8_t *conv_frame_buf;
|
2011-01-04 14:44:05 +00:00
|
|
|
int64_t frame_cnt;
|
2011-01-03 16:51:17 +00:00
|
|
|
|
|
|
|
uint8_t *outbuf;
|
|
|
|
size_t outbuf_size;
|
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
AVFormatContext *format;
|
|
|
|
|
2011-01-03 16:51:17 +00:00
|
|
|
} video;
|
|
|
|
|
|
|
|
struct audio_info
|
|
|
|
{
|
|
|
|
bool enabled;
|
|
|
|
AVCodecContext *codec;
|
|
|
|
|
|
|
|
int16_t *buffer;
|
|
|
|
size_t frames_in_buffer;
|
|
|
|
|
|
|
|
void *outbuf;
|
|
|
|
size_t outbuf_size;
|
|
|
|
} audio;
|
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
struct muxer_info
|
|
|
|
{
|
|
|
|
AVFormatContext *ctx;
|
|
|
|
AVStream *astream;
|
|
|
|
AVStream *vstream;
|
|
|
|
};
|
|
|
|
|
2011-01-03 16:51:17 +00:00
|
|
|
struct ffemu
|
|
|
|
{
|
|
|
|
struct video_info video;
|
|
|
|
struct audio_info audio;
|
2011-01-04 14:44:05 +00:00
|
|
|
struct muxer_info muxer;
|
2011-01-03 16:51:17 +00:00
|
|
|
|
|
|
|
struct ffemu_params params;
|
|
|
|
};
|
|
|
|
|
2011-01-03 19:46:50 +00:00
|
|
|
static int map_audio_codec(ffemu_audio_codec codec)
|
2011-01-03 16:51:17 +00:00
|
|
|
{
|
2011-01-03 19:46:50 +00:00
|
|
|
(void)codec;
|
|
|
|
return CODEC_ID_AAC;
|
2011-01-03 16:51:17 +00:00
|
|
|
}
|
|
|
|
|
2011-01-03 19:46:50 +00:00
|
|
|
static int map_video_codec(ffemu_video_codec codec)
|
2011-01-03 16:51:17 +00:00
|
|
|
{
|
|
|
|
(void)codec;
|
2011-01-04 14:44:05 +00:00
|
|
|
return CODEC_ID_H264;
|
2011-01-03 16:51:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int init_audio(struct audio_info *audio, struct ffemu_params *param)
|
|
|
|
{
|
|
|
|
AVCodec *codec = avcodec_find_encoder(map_audio_codec(param->acodec));
|
|
|
|
if (!codec)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
audio->codec = avcodec_alloc_context();
|
2011-01-04 14:44:05 +00:00
|
|
|
audio->codec->bit_rate = 192000;
|
2011-01-03 16:51:17 +00:00
|
|
|
audio->codec->sample_rate = param->samplerate;
|
|
|
|
audio->codec->channels = param->channels;
|
|
|
|
|
|
|
|
if (avcodec_open(audio->codec, codec) != 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
audio->buffer = av_malloc(audio->codec->frame_size * param->channels * sizeof(int16_t));
|
|
|
|
if (!audio->buffer)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
audio->outbuf_size = 50000;
|
|
|
|
audio->outbuf = av_malloc(audio->outbuf_size);
|
|
|
|
if (!audio->outbuf)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
static void init_x264_param(AVCodecContext *c)
|
|
|
|
{
|
|
|
|
c->coder_type = 1; // coder = 1
|
|
|
|
c->flags|=CODEC_FLAG_LOOP_FILTER; // flags=+loop
|
|
|
|
c->me_cmp|= 1; // cmp=+chroma, where CHROMA = 1
|
|
|
|
c->partitions|=X264_PART_I8X8+X264_PART_I4X4+X264_PART_P8X8+X264_PART_B8X8; // partitions=+parti8x8+parti4x4+partp8x8+partb8x8
|
|
|
|
c->me_method=ME_HEX; // me_method=hex
|
|
|
|
c->me_subpel_quality = 7; // subq=7
|
|
|
|
c->me_range = 16; // me_range=16
|
|
|
|
c->gop_size = 250; // g=250
|
|
|
|
c->keyint_min = 25; // keyint_min=25
|
|
|
|
c->scenechange_threshold = 40; // sc_threshold=40
|
|
|
|
c->i_quant_factor = 0.71; // i_qfactor=0.71
|
|
|
|
c->b_frame_strategy = 1; // b_strategy=1
|
|
|
|
c->qcompress = 0.6; // qcomp=0.6
|
|
|
|
c->qmin = 10; // qmin=10
|
|
|
|
c->qmax = 51; // qmax=51
|
|
|
|
c->max_qdiff = 4; // qdiff=4
|
|
|
|
c->max_b_frames = 3; // bf=3
|
|
|
|
c->refs = 3; // refs=3
|
|
|
|
c->directpred = 1; // directpred=1
|
|
|
|
c->trellis = 1; // trellis=1
|
|
|
|
c->flags2|=CODEC_FLAG2_BPYRAMID+CODEC_FLAG2_MIXED_REFS+CODEC_FLAG2_WPRED+CODEC_FLAG2_8X8DCT+CODEC_FLAG2_FASTPSKIP; // flags2=+bpyramid+mixed_refs+wpred+dct8x8+fastpskip
|
|
|
|
c->weighted_p_pred = 2; // wpredp=2
|
|
|
|
|
|
|
|
// libx264-main.ffpreset preset
|
|
|
|
c->flags2|=CODEC_FLAG2_8X8DCT;c->flags2^=CODEC_FLAG2_8X8DCT;
|
|
|
|
}
|
|
|
|
|
2011-01-03 19:46:50 +00:00
|
|
|
static int init_video(struct video_info *video, struct ffemu_params *param)
|
|
|
|
{
|
|
|
|
AVCodec *codec = avcodec_find_encoder(map_video_codec(param->vcodec));
|
|
|
|
if (!codec)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
video->codec = avcodec_alloc_context();
|
|
|
|
video->codec->width = param->out_width;
|
|
|
|
video->codec->height = param->out_height;
|
|
|
|
video->codec->time_base = (AVRational) {param->fps.den, param->fps.num};
|
2011-01-04 14:44:05 +00:00
|
|
|
video->codec->crf = 23;
|
2011-01-03 19:46:50 +00:00
|
|
|
video->codec->pix_fmt = PIX_FMT_YUV420P;
|
2011-01-04 14:44:05 +00:00
|
|
|
init_x264_param(video->codec);
|
2011-01-03 19:46:50 +00:00
|
|
|
|
|
|
|
if (avcodec_open(video->codec, codec) != 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
video->outbuf_size = 100000;
|
|
|
|
video->outbuf = av_malloc(video->outbuf_size);
|
|
|
|
|
|
|
|
int size = avpicture_get_size(PIX_FMT_YUV420P, param->out_width, param->out_height);
|
|
|
|
video->conv_frame_buf = av_malloc(size);
|
|
|
|
video->conv_frame = avcodec_alloc_frame();
|
|
|
|
avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, PIX_FMT_YUV420P, param->out_width, param->out_height);
|
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int init_muxer(ffemu_t *handle)
|
|
|
|
{
|
|
|
|
AVFormatContext *ctx = avformat_alloc_context();
|
|
|
|
av_strlcpy(ctx->filename, handle->params.filename, sizeof(ctx->filename));
|
|
|
|
ctx->oformat = av_guess_format(NULL, ctx->filename, NULL);
|
|
|
|
if (url_fopen(&ctx->pb, ctx->filename, URL_WRONLY) < 0)
|
|
|
|
{
|
|
|
|
av_free(ctx);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int stream_cnt = 0;
|
|
|
|
if (handle->video.enabled)
|
|
|
|
{
|
|
|
|
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
|
|
|
stream->codec = handle->video.codec;
|
|
|
|
|
|
|
|
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
|
|
|
handle->video.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
|
|
|
handle->muxer.vstream = stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handle->audio.enabled)
|
|
|
|
{
|
|
|
|
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
|
|
|
stream->codec = handle->audio.codec;
|
|
|
|
|
|
|
|
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
|
|
|
handle->audio.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
|
|
|
handle->muxer.astream = stream;
|
|
|
|
}
|
2011-01-03 19:46:50 +00:00
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
if (av_write_header(ctx) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
handle->muxer.ctx = ctx;
|
2011-01-03 19:46:50 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-01-03 16:51:17 +00:00
|
|
|
ffemu_t *ffemu_new(const struct ffemu_params *params)
|
|
|
|
{
|
|
|
|
avcodec_init();
|
|
|
|
av_register_all();
|
|
|
|
|
|
|
|
ffemu_t *handle = calloc(1, sizeof(*handle));
|
|
|
|
if (!handle)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
handle->params = *params;
|
|
|
|
if (handle->params.vcodec != FFEMU_VIDEO_NONE)
|
|
|
|
handle->video.enabled = true;
|
|
|
|
if (handle->params.acodec != FFEMU_AUDIO_NONE)
|
|
|
|
handle->audio.enabled = true;
|
|
|
|
|
|
|
|
if (handle->video.enabled)
|
|
|
|
if (init_video(&handle->video, &handle->params) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (handle->audio.enabled)
|
|
|
|
if (init_audio(&handle->audio, &handle->params) < 0)
|
|
|
|
goto error;
|
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
if (init_muxer(handle) < 0)
|
|
|
|
goto error;
|
|
|
|
|
2011-01-03 16:51:17 +00:00
|
|
|
return handle;
|
|
|
|
|
|
|
|
error:
|
|
|
|
ffemu_free(handle);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ffemu_free(ffemu_t *handle)
|
|
|
|
{
|
|
|
|
if (handle)
|
|
|
|
{
|
|
|
|
if (handle->audio.codec)
|
|
|
|
{
|
|
|
|
avcodec_close(handle->audio.codec);
|
|
|
|
av_free(handle->audio.codec);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handle->audio.buffer)
|
|
|
|
av_free(handle->audio.buffer);
|
|
|
|
|
|
|
|
if (handle->video.codec)
|
|
|
|
{
|
|
|
|
avcodec_close(handle->video.codec);
|
|
|
|
av_free(handle->video.codec);
|
|
|
|
}
|
|
|
|
|
2011-01-03 19:46:50 +00:00
|
|
|
if (handle->video.conv_frame)
|
|
|
|
av_free(handle->video.conv_frame);
|
|
|
|
|
|
|
|
if (handle->video.conv_frame_buf)
|
|
|
|
av_free(handle->video.conv_frame_buf);
|
2011-01-03 16:51:17 +00:00
|
|
|
|
|
|
|
free(handle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
|
|
|
|
{
|
|
|
|
if (!handle->video.enabled)
|
|
|
|
return -1;
|
2011-01-03 19:46:50 +00:00
|
|
|
|
|
|
|
struct SwsContext *conv_ctx = sws_getContext(data->width, data->height, PIX_FMT_RGB555LE,
|
|
|
|
handle->params.out_width, handle->params.out_height, PIX_FMT_YUV420P, SWS_BICUBIC,
|
|
|
|
NULL, NULL, NULL);
|
|
|
|
|
|
|
|
int linesize = data->pitch;
|
|
|
|
|
|
|
|
sws_scale(conv_ctx, (const uint8_t* const*)&data->data, &linesize, 0, handle->params.out_width, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
|
|
|
|
|
|
|
int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, handle->video.conv_frame);
|
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
//fwrite(handle->video.outbuf, 1, outsize, handle->video.file);
|
2011-01-03 19:46:50 +00:00
|
|
|
|
|
|
|
sws_freeContext(conv_ctx);
|
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
AVPacket pkt;
|
|
|
|
av_init_packet(&pkt);
|
|
|
|
pkt.stream_index = handle->muxer.vstream->index;
|
|
|
|
pkt.data = handle->video.outbuf;
|
|
|
|
pkt.size = outsize;
|
|
|
|
pkt.pts = av_rescale_q(handle->video.frame_cnt++, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
|
|
|
pkt.dts = pkt.pts;
|
|
|
|
|
|
|
|
if (av_interleaved_write_frame(handle->muxer.ctx, &pkt) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2011-01-03 16:51:17 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
|
|
|
{
|
2011-01-03 19:46:50 +00:00
|
|
|
if (!handle->audio.enabled)
|
|
|
|
return -1;
|
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
AVPacket pkt;
|
|
|
|
av_init_packet(&pkt);
|
|
|
|
pkt.stream_index = handle->muxer.astream->index;
|
|
|
|
pkt.pts = AV_NOPTS_VALUE;
|
|
|
|
pkt.dts = pkt.pts;
|
|
|
|
pkt.data = handle->audio.outbuf;
|
|
|
|
|
2011-01-03 16:51:17 +00:00
|
|
|
size_t written_frames = 0;
|
|
|
|
while (written_frames < data->frames)
|
|
|
|
{
|
|
|
|
size_t can_write = handle->audio.codec->frame_size - handle->audio.frames_in_buffer;
|
|
|
|
size_t write_frames = data->frames > can_write ? can_write : data->frames;
|
|
|
|
|
|
|
|
memcpy(handle->audio.buffer + handle->audio.frames_in_buffer * handle->params.channels,
|
|
|
|
data->data + written_frames * handle->params.channels,
|
|
|
|
write_frames * handle->params.channels * sizeof(int16_t));
|
|
|
|
|
|
|
|
written_frames += write_frames;
|
|
|
|
handle->audio.frames_in_buffer += write_frames;
|
|
|
|
|
|
|
|
if (handle->audio.frames_in_buffer == (size_t)handle->audio.codec->frame_size)
|
|
|
|
{
|
|
|
|
size_t out_size = avcodec_encode_audio(handle->audio.codec, handle->audio.outbuf, handle->audio.outbuf_size, handle->audio.buffer);
|
2011-01-04 14:44:05 +00:00
|
|
|
//fwrite(handle->audio.outbuf, 1, out_size, handle->audio.file);
|
|
|
|
pkt.size = out_size;
|
2011-01-03 16:51:17 +00:00
|
|
|
handle->audio.frames_in_buffer = 0;
|
2011-01-04 14:44:05 +00:00
|
|
|
|
|
|
|
if (av_interleaved_write_frame(handle->muxer.ctx, &pkt) < 0)
|
|
|
|
return -1;
|
2011-01-03 16:51:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-01-04 14:44:05 +00:00
|
|
|
int ffemu_finalize(ffemu_t *handle)
|
2011-01-03 16:51:17 +00:00
|
|
|
{
|
2011-01-04 14:44:05 +00:00
|
|
|
// Push out delayed frames.
|
|
|
|
if (handle->video.enabled)
|
|
|
|
{
|
|
|
|
AVPacket pkt;
|
|
|
|
av_init_packet(&pkt);
|
|
|
|
pkt.stream_index = handle->muxer.vstream->index;
|
|
|
|
pkt.data = handle->video.outbuf;
|
|
|
|
|
|
|
|
int out_size = 0;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
out_size = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, NULL);
|
|
|
|
|
|
|
|
pkt.size = out_size;
|
|
|
|
pkt.pts = av_rescale_q(handle->video.frame_cnt++, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
|
|
|
pkt.dts = pkt.pts;
|
|
|
|
|
|
|
|
int err = av_interleaved_write_frame(handle->muxer.ctx, &pkt);
|
|
|
|
if (err < 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
} while (out_size > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
av_write_trailer(handle->muxer.ctx);
|
|
|
|
|
|
|
|
return 0;
|
2011-01-03 16:51:17 +00:00
|
|
|
}
|
|
|
|
|