/* * libpipi Pathetic image processing interface library * Copyright (c) 2004-2009 Sam Hocevar * All Rights Reserved * * $Id$ * * This library is free software. It comes without any warranty, to * the extent permitted by applicable law. You can redistribute it * and/or modify it under the terms of the Do What The Fuck You Want * To Public License, Version 2, as published by Sam Hocevar. See * http://sam.zoy.org/wtfpl/COPYING for more details. */ /* * codec.c: image I/O functions */ #include "config.h" #if defined _WIN32 # undef _CRT_SECURE_NO_WARNINGS # define _CRT_SECURE_NO_WARNINGS /* I know how to use snprintf, thank you */ # define snprintf _snprintf #endif #include #include #include #if defined USE_FFMPEG # include # include #endif #include "pipi.h" #include "pipi_internals.h" #if defined USE_FFMPEG typedef struct { uint8_t *buf; size_t buf_len; AVFormatContext *fmt_ctx; AVStream *stream; AVCodecContext *cod_ctx; AVCodec *codec; AVFrame *frame; struct SwsContext *sws_ctx; int src_width, src_height, src_fmt; } ffmpeg_codec_t; #endif pipi_sequence_t *pipi_open_sequence(char const *file, int width, int height, int rgb, int fps, int par_num, int par_den, int bitrate) { #if defined USE_FFMPEG static int initialised = 0; pipi_sequence_t *seq; ffmpeg_codec_t *ff; uint8_t *tmp; seq = malloc(sizeof(pipi_sequence_t)); seq->w = width; seq->h = height; seq->fps = fps; seq->convert_buf = NULL; ff = malloc(sizeof(ffmpeg_codec_t)); memset(ff, 0, sizeof(*ff)); seq->codec_priv = ff; if (!initialised) { av_register_all(); initialised = 1; } ff->fmt_ctx = avformat_alloc_context(); if (!ff->fmt_ctx) goto error; /* Careful here: the Win32 snprintf doesn't seem to add a trailing * zero to the truncated output. */ snprintf(ff->fmt_ctx->filename, sizeof(ff->fmt_ctx->filename), file); ff->fmt_ctx->filename[sizeof(ff->fmt_ctx->filename) - 1] = '\0'; ff->fmt_ctx->oformat = av_guess_format(NULL, file, NULL); if (!ff->fmt_ctx->oformat) ff->fmt_ctx->oformat = av_guess_format("mpeg", NULL, NULL); if (!ff->fmt_ctx->oformat) goto error; ff->stream = av_new_stream(ff->fmt_ctx, 0); if (!ff->stream) goto error; ff->stream->sample_aspect_ratio.num = par_num; ff->stream->sample_aspect_ratio.den = par_den; ff->cod_ctx = ff->stream->codec; ff->cod_ctx->width = width; ff->cod_ctx->height = height; ff->cod_ctx->sample_aspect_ratio.num = par_num; ff->cod_ctx->sample_aspect_ratio.den = par_den; ff->cod_ctx->codec_id = ff->fmt_ctx->oformat->video_codec; ff->cod_ctx->codec_type = CODEC_TYPE_VIDEO; ff->cod_ctx->bit_rate = bitrate; ff->cod_ctx->time_base.num = 1; ff->cod_ctx->time_base.den = fps; ff->cod_ctx->pix_fmt = PIX_FMT_YUV420P; /* send YUV 420 */ if (ff->cod_ctx->codec_id == CODEC_ID_MPEG2VIDEO) ff->cod_ctx->max_b_frames = 2; if (ff->cod_ctx->codec_id == CODEC_ID_MPEG1VIDEO) ff->cod_ctx->mb_decision = 2; if (ff->cod_ctx->codec_id == CODEC_ID_H264) { /* Import x264 slow presets */ ff->cod_ctx->coder_type = 1; ff->cod_ctx->flags |= CODEC_FLAG_LOOP_FILTER; ff->cod_ctx->me_cmp |= FF_CMP_CHROMA; ff->cod_ctx->partitions |= X264_PART_I4X4 | X264_PART_I8X8 | X264_PART_P4X4 | X264_PART_P8X8; ff->cod_ctx->me_method = ME_UMH; ff->cod_ctx->me_subpel_quality = 8; ff->cod_ctx->me_range = 16; ff->cod_ctx->gop_size = 250; ff->cod_ctx->keyint_min = 25; ff->cod_ctx->scenechange_threshold = 40; ff->cod_ctx->i_quant_factor = 0.71f; ff->cod_ctx->b_frame_strategy = 2; ff->cod_ctx->qcompress = 0.6f; ff->cod_ctx->qmin = 10; ff->cod_ctx->qmax = 51; ff->cod_ctx->max_qdiff = 4; ff->cod_ctx->max_b_frames = 3; ff->cod_ctx->refs = 5; ff->cod_ctx->directpred = 3; ff->cod_ctx->trellis = 1; ff->cod_ctx->flags2 |= CODEC_FLAG2_BPYRAMID | CODEC_FLAG2_MIXED_REFS | CODEC_FLAG2_WPRED | CODEC_FLAG2_8X8DCT | CODEC_FLAG2_FASTPSKIP; ff->cod_ctx->weighted_p_pred = 2; ff->cod_ctx->rc_lookahead = 50; } if (ff->fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) ff->cod_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; if (av_set_parameters(ff->fmt_ctx, NULL) < 0) goto error; ff->codec = avcodec_find_encoder(ff->cod_ctx->codec_id); if (!ff->codec) goto error; if (avcodec_open(ff->cod_ctx, ff->codec) < 0) goto error; ff->frame = avcodec_alloc_frame(); if (!ff->frame) goto error; tmp = (uint8_t *)av_malloc(avpicture_get_size(ff->cod_ctx->pix_fmt, ff->cod_ctx->width, ff->cod_ctx->height)); if (!tmp) goto error; avpicture_fill((AVPicture *)ff->frame, tmp, ff->cod_ctx->pix_fmt, ff->cod_ctx->width, ff->cod_ctx->height); if (!(ff->fmt_ctx->flags & AVFMT_NOFILE)) if (url_fopen(&ff->fmt_ctx->pb, file, URL_WRONLY) < 0) goto error; ff->buf_len = 64 * 1024 * 1024; ff->buf = (uint8_t *)av_malloc(ff->buf_len); ff->src_fmt = rgb ? PIX_FMT_RGB32 : PIX_FMT_YUV444P; av_write_header(ff->fmt_ctx); return seq; error: pipi_close_sequence(seq); return NULL; #else return NULL; #endif } int pipi_feed_sequence(pipi_sequence_t *seq, uint8_t const *buffer, int width, int height) { #if defined USE_FFMPEG AVPacket packet; uint8_t const *buflist[3]; int pitchlist[3]; size_t bytes; int n; ffmpeg_codec_t *ff = (ffmpeg_codec_t *)seq->codec_priv; if (ff->src_width != width || ff->src_height != height) { ff->src_width = width; ff->src_height = height; if (ff->sws_ctx) sws_freeContext(ff->sws_ctx); ff->sws_ctx = NULL; } if (!ff->sws_ctx) { ff->sws_ctx = sws_getContext(width, height, ff->src_fmt, ff->cod_ctx->width, ff->cod_ctx->height, ff->cod_ctx->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL); if (seq->convert_buf) { free(seq->convert_buf); seq->convert_buf = NULL; } } if (!ff->sws_ctx) return -1; /* Convert interleaved YUV to planar YUV */ if (ff->src_fmt == PIX_FMT_YUV444P) { if (!seq->convert_buf) seq->convert_buf = malloc(width * height * 3); for (n = 0; n < width * height; n++) { seq->convert_buf[n] = buffer[4 * n]; seq->convert_buf[n + width * height] = buffer[4 * n + 1]; seq->convert_buf[n + 2 * width * height] = buffer[4 * n + 2]; } /* Feed the buffers to FFmpeg */ buflist[0] = seq->convert_buf; buflist[1] = seq->convert_buf + 2 * width * height; buflist[2] = seq->convert_buf + width * height; pitchlist[0] = pitchlist[1] = pitchlist[2] = width; } else { buflist[0] = buffer; pitchlist[0] = 4 * width; } sws_scale(ff->sws_ctx, buflist, pitchlist, 0, height, ff->frame->data, ff->frame->linesize); bytes = avcodec_encode_video(ff->cod_ctx, ff->buf, ff->buf_len, ff->frame); if (bytes <= 0) return 0; av_init_packet(&packet); if (ff->cod_ctx->coded_frame->pts != 0x8000000000000000LL) packet.pts = av_rescale_q(ff->cod_ctx->coded_frame->pts, ff->cod_ctx->time_base, ff->stream->time_base); if (ff->cod_ctx->coded_frame->key_frame) packet.flags |= PKT_FLAG_KEY; packet.stream_index = 0; packet.data = ff->buf; packet.size = bytes; if (av_interleaved_write_frame(ff->fmt_ctx, &packet) < 0) return -1; #endif return 0; } int pipi_close_sequence(pipi_sequence_t *seq) { #if defined USE_FFMPEG ffmpeg_codec_t *ff = (ffmpeg_codec_t *)seq->codec_priv; /* Finish the sequence */ if (ff->buf) { av_write_trailer(ff->fmt_ctx); } /* Close everything */ if (ff->buf) { av_free(ff->buf); ff->buf = NULL; } if (ff->cod_ctx) { avcodec_close(ff->cod_ctx); ff->cod_ctx = NULL; } if (ff->frame) { av_free(ff->frame->data[0]); av_free(ff->frame); ff->frame = NULL; } if (ff->fmt_ctx) { av_freep(&ff->fmt_ctx->streams[0]->codec); ff->codec = NULL; av_freep(&ff->fmt_ctx->streams[0]); ff->stream = NULL; if (!(ff->fmt_ctx->flags & AVFMT_NOFILE) && ff->fmt_ctx->pb) url_fclose(ff->fmt_ctx->pb); av_free(ff->fmt_ctx); ff->fmt_ctx = NULL; } if (ff->sws_ctx) { sws_freeContext(ff->sws_ctx); ff->sws_ctx = NULL; ff->src_width = ff->src_height = 0; } free(ff); free(seq); #endif return 0; }