FFmpeg将BGRA帧编码成视频

源码

将外部传进来的BGRA帧编码成各种格式的视频文件
基于FFFmpeg框架,将外部传来的每一BGRA帧进行编码,生成视频文件,然后将视频和外部传来的MP3文件重新复用(mux),生成各种格式的视频。

common.h  
导入FFmpeg需要的头文件和库

encode.h    
   对外提供了5个接口:    

  • preparing_to_push //设置视频参数和FFmpeg相关结构体,创建编码线程encode_thread  
  • pushing_frame     //外部传入一帧(倒转的RGBA帧),将帧写入AVFifoBuffer中  
  • ending_push       //当外部没有帧了,将is_finished标志位置1,用以通知编码线程要停止编码    
  • setting_stop      //当用户按了停止键,将is_stop标志位置1,通知编码线程退出循坏,提前结束编码  
  •  releasing         //释放结构体动态分配的内存,锁资源,删除临时文件(如果有)  

encode.cpp    
编码的具体实现,生成某种格式的容器(mp4,avi,wmv,flv,mov,透明通道mov)  
 相关技术:多线程,条件变量,锁,回调函数    

mux.h   
保存之前编码的结构体videoParm,添加mux参数     
  
mux.cpp   
 将encode生成的视频文件(无音频)和外部传来的音频文件(.mp3)remux成新的视频文件   

common.h

#ifdef __cplusplus
extern "C"
{
#endif

#include "pthread.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/avutil.h"
#include "libavutil/imgutils.h"
#include "libavutil/pixfmt.h"
#include "libavutil/time.h"
#include "libavfilter/avfilter.h"

#pragma comment(lib,"pthreadVC2.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"swresample.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"postproc.lib")
#pragma comment(lib,"swscale.lib")
#pragma comment(lib,"avfilter.lib")

#ifdef __cplusplus
};
#endif

encode.h

#ifdef __cplusplus
extern "C"
{
#endif

#include"common.h"  
#include"libavutil\fifo.h"

	typedef struct pthreadParm {
		/* 线程id */
		pthread_t tid;
		/* 互斥锁 */
		pthread_mutex_t mutex_t;
		/* 条件等待 */
		pthread_cond_t cond_t;
	}pthread_Parm;

	/* 视频参数结构体,方便传参数*/
	typedef struct videoParms
	{
		AVFormatContext *fmt_ctx;
		AVCodecContext *codec_ctx;
		AVStream *stream;
		SwsContext *sws_ctx;
		AVFrame *argb_frame;
		AVFrame *yuv_frame;
		int argb_size;
		int yuv_size;
		/* 输入的argb帧 */
		uint8_t *argb_buf;
		/* 如果不是透明通道,则需转为yuv帧 */
		uint8_t *yuv_buf;
		AVPacket *pkt;
		/* 帧宽度 */
		int width;
		/* 帧高度 */
		int height;
		/* 帧率 */
		int fps;
		/* 输出视频格式 */
		const char *format;
		/* 输出路径 */
		const char *path;
		/* 强制结束标志 */
		bool is_stop;
		/* 编码结束标志 */
		bool is_finished;
		/* 当前帧数 */
		int current_count;
		pthreadParm *pp;
		/* 写入缓存 */
		AVFifoBuffer *fifo_buf;
		/* 回调函数 */
		void *notification;
		/* FREContext */
		void *current_ctx;
		/* 临时路径 */
		char *tmp_path;
		/* 进度 */
		int progress;
		/* 音频有无标志,如果没有音频不生成临时文件,有音频要生成临时文件 */
		int audio_exist;
		AVBitStreamFilterContext *bsfc;
	}videoParm;

	/*
	*identifier,status,percent
	*/
	typedef void(*EncodeCallbackFunction)(void*, int, int);

	/* 输入宽,高,帧率,输出视频格式,输出路径,音频存在标志。
	* 该函数为结构体分配内存,然后创建编码线程,返回指向结构体的指针
	*/
	extern void *preparing_to_push(int width, int height, int fps, int audio_exist, const char *format, const char *path);

	/*
	* 输入结构体指针,外部传入一帧(倒转的RGBA帧),将帧写入AVFifoBuffer中
	*/
	extern int pushing_frame(void *rawdatabuf, void *videoparam);

	/*
	* 当外部没有帧了,将is_finished标志位置1,用以通知编码线程要停止编码了
	*/
	extern void ending_push(void *videoparam);

	/*
	* 当用户按了停止键,将is_stop标志位置1,通知编码线程推出循坏,提前结束编码
	*/
	extern void setting_stop(void *videoparm);

	/*
	* 释放结构体动态分配的内存,锁资源,删除临时文件(如果有)
	*/
	extern void releasing(videoParm *vp);

#ifdef __cplusplus
};
#endif

encode.cpp

#include "stdafx.h"

//#define DEBUG_ 

#ifdef DEBUG_
#include<timeapi.h>
#pragma comment(lib, "winmm.lib ")
#endif

#define REMOVE_TEMP_FILE

#ifdef __cplusplus
extern "C"
{
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>

#include"common.h"

#include <io.h>
#include<direct.h>

#include"encode.h"
#include"logger.h"

#ifdef __cplusplus
};
#endif

#define ERR_NULL_EXIT {callback_function((EncodeCallbackFunction)vp->notification, vp, -1, 0);\
					   return NULL;\
}

static void* encode_thread(void *videoparm);

/*
* 将编码器中剩余的几帧写入文件
*/
static void flush_encode(videoParm *vp);

/*
* end_push后将fifo_buf中剩余的帧写入文件
*/
static int encode_remaining(videoParm *vp);

/*
* 填充codec_ctx, 和dict,平衡比特率,编码速度,画质
*/
static void codecCtx_fill(AVCodecContext *codec_ctx, AVDictionary **dict, const char *out_fmt);

/*
* 分配argb帧(yuv帧)内存,先让data指针和linesize指针指向buf,后面再从文件中写入数据到buf
*/
static int frame_buf_alloc(videoParm *vp, int width, int height);

/*
* 该源文件中返回的百分比都是0,因为encode的时候的进度由外部计算,到add_audio的时候要直接算
*/
static void callback_function(EncodeCallbackFunction fun, void *identifier, int status, int percent);

static void encode_delay_time(pthread_mutex_t *p_mutex, pthread_cond_t *p_cond, uint64_t ms);

/*
* 编码时创建的临时文件,用以在mux.cpp中与传入的音频合成
*/
static char* EncodeCreateTempFile(const char *ext);

static char* encode_parm_copy(const char *src);

/* ANSI */
static char *EncodeCreateTempDir(char *sub_dir);

#ifdef DEBUG_
/* 监视push帧的次数 */
int g_push_cnt = 0;
/* 监视实际写入帧的次数 */
int g_write_frame_cnt = 0;
#endif

/* 由于线程是异步的,当外部调用preparing_to_push函数后,外部传进来的指针变量如format或path,实际内存已经释放
* 所以需要在prepare函数动态分配内存保存指针变量
* 这些动态分配内存的变量最终在release函数中释放内存
*/
static char* encode_parm_copy(const char *src)
{
	char *dst;
	size_t len = strlen(src) + 1;
	dst = (char *)malloc(len);
	if (dst == NULL) {
		LOG_PRINT("%s....Malloc failed", __FUNCTION__);
		return NULL;
	}
	memset(dst, 0, len);
	memcpy(dst, src, strlen(src));
	return dst;
}

preparing_to_push:

void *preparing_to_push(int width, int height, int fps, int audio_exist, const char *format, const char *path)
{

	__try {
		LOG_PRINT("%s....1", __FUNCTION__);
#ifdef DEBUG_
		g_push_cnt = 0;
		g_write_frame_cnt = 0;
#endif
		videoParm *vp = (videoParm*)av_malloc(sizeof(videoParm));
		if (vp == NULL)
		{
			LOG_PRINT("%s....videoParm allocation failed", __FUNCTION__);
			ERR_NULL_EXIT
		}
		memset(vp, 0, sizeof(videoParm));

		vp->fmt_ctx = NULL;
		vp->codec_ctx = NULL;
		vp->stream = NULL;
		vp->sws_ctx = NULL;
		vp->width = width;
		vp->height = height;
		vp->fps = fps;
		vp->audio_exist = audio_exist;
		//vp->audio_exist = TRUE;
		/* Malloc内存保存char*变量foramt,path,防止主线程结束,子线程变量丢失内容 */
		vp->format = encode_parm_copy(format);
		vp->path = encode_parm_copy(path);
		if (vp->format == NULL || vp->path == NULL) {
			LOG_PRINT("%s....encode_parm_copy failed", __FUNCTION__);
			ERR_NULL_EXIT
		}
		vp->pp = (pthread_Parm*)malloc(sizeof(pthread_Parm));
		memset(vp->pp, 0, sizeof(pthread_Parm));
		vp->pp->tid.p = NULL;
		vp->pp->tid.x = 0;

		vp->is_stop = FALSE;
		vp->is_finished = FALSE;
		vp->current_count = 0;
		vp->yuv_frame = NULL;
		vp->yuv_buf = NULL;
		vp->yuv_size = 0;
		vp->sws_ctx = NULL;
		vp->notification = NULL;
		vp->current_ctx = NULL;
		pthread_mutex_init(&vp->pp->mutex_t, NULL);
		pthread_cond_init(&vp->pp->cond_t, NULL);

		vp->progress = 0;
		LOG_PRINT("%s....width = %d, height = %d, fps = %d, format = %s, outpath = %s, audio_exit = %d", __FUNCTION__,
			vp->width, vp->height, fps, vp->format, vp->path, vp->audio_exist);
		LOG_PRINT("%s...vp->fps = %d", __FUNCTION__, vp->fps);
		/*void *call_back = &encode_callback_fun;
		vp->notification = call_back;*/

		int ret;
		/* 分配packet内存 */
		vp->pkt = av_packet_alloc();
		//AVPacket pkt;
		AVCodec *codec = NULL;
		AVDictionary* dict = NULL;

		//分配给fifo_buf的大小:10帧
		vp->fifo_buf = av_fifo_alloc(width * height * 4 * 10);
		if (vp->fifo_buf == NULL)
		{
			LOG_PRINT("%s....av_fifo_alloc failed", __FUNCTION__);
			ERR_NULL_EXIT
		}

		avcodec_register_all();
		av_register_all();

		char tmp_name[200];
		/* avformat_alloc_output_context2 只用format_name判断格式时,当format_name = mkv和wav时会判断不了*/
		if (!strcmp("alpha_mov", vp->format))
			sprintf_s(tmp_name, 200, "guess.mov");
		else
			sprintf_s(tmp_name, 200, "guess.%s", vp->format);
		AVOutputFormat *opFmt = av_guess_format(NULL, tmp_name, NULL);
		/* vp->fps < 0 indicates that there is no audio input */

		/* 音频存在则创建临时文件 */
		if (!audio_exist) {
			//vp->fps = -fps;
			vp->tmp_path = NULL;
			ret = avformat_alloc_output_context2(&vp->fmt_ctx, opFmt, vp->format, vp->path);
		}
		else {
			//vp->fps = fps;
			vp->tmp_path = EncodeCreateTempFile(vp->format);	//must free this variable ahead of freeing of Structure
			if (vp->tmp_path == NULL) {
				LOG_PRINT("%s....CreateTempFile failed", __FUNCTION__);
				ERR_NULL_EXIT
			}
			ret = avformat_alloc_output_context2(&vp->fmt_ctx, opFmt, vp->format, vp->tmp_path);
		}
		if (ret < 0)
		{
			LOG_PRINT("%s....avformat_alloc_output_context2 failed", __FUNCTION__);
			ERR_NULL_EXIT
		}

		vp->stream = avformat_new_stream(vp->fmt_ctx, NULL);
		if (vp->stream == NULL)
		{
			LOG_PRINT("%s....avformat_new_stream failed", __FUNCTION__);
			ERR_NULL_EXIT
		}
		vp->codec_ctx = avcodec_alloc_context3(NULL);
		if (vp->codec_ctx == NULL)
		{
			LOG_PRINT("%s....avcodec_alloc_context3 failed", __FUNCTION__);
			ERR_NULL_EXIT
		}
		//vp->codec_ctx->b_frame_strategy
		/* 选择encoder和pix_fmt */
		codecCtx_fill(vp->codec_ctx, &dict, format);
		vp->codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
		vp->codec_ctx->width = vp->width;
		vp->codec_ctx->height = vp->height;

		vp->codec_ctx->time_base.num = 1;
		vp->codec_ctx->time_base.den = vp->fps;

		//vp->stream->time_base = { 1, vp->fps };
		vp->stream->time_base.num = 1;
		vp->stream->time_base.den = vp->fps;

		/* 查找编码器 */
		codec = avcodec_find_encoder(vp->codec_ctx->codec_id);
		if (codec == NULL)
		{
			LOG_PRINT("%s....avcodec_find_encoder failed", __FUNCTION__);
			ERR_NULL_EXIT
		}

		vp->codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

		/* 打开编码器 */
		ret = avcodec_open2(vp->codec_ctx, codec, &dict);
		if (ret != 0)
		{
			LOG_PRINT("%s....avcodec_open2 failed", __FUNCTION__);
			ERR_NULL_EXIT
		}
		/* copy the stream parameters to the muxer */
		ret = avcodec_parameters_from_context(vp->stream->codecpar, vp->codec_ctx);
		if (ret < 0)
		{
			LOG_PRINT("%s....avcodec_parameters_from_context failed", __FUNCTION__);
			ERR_NULL_EXIT
		}

		/* 填充argb_frame和yuv_frame(如果有) */
		if (frame_buf_alloc(vp, width, height)) {
			LOG_PRINT("%s....frame_buf_alloc failed", __FUNCTION__);
			ERR_NULL_EXIT
		}

#ifdef DEBUG_
		//打印信息
		if (vp->tmp_path == NULL)
			av_dump_format(vp->fmt_ctx, 0, vp->path, 1);
		else
			av_dump_format(vp->fmt_ctx, 0, vp->tmp_path, 1);
#endif

		/* 读取输出文件到缓存中,接下来要对其进行写入 */
		if (vp->tmp_path == NULL)
			ret = avio_open(&vp->fmt_ctx->pb, vp->path, AVIO_FLAG_WRITE);
		else
			ret = avio_open(&vp->fmt_ctx->pb, vp->tmp_path, AVIO_FLAG_WRITE);
		if (ret < 0)
		{
			LOG_PRINT("%s....avio_open failed", __FUNCTION__);
			ERR_NULL_EXIT
		}

		/* 将帧的头部写入输出文件 */
		ret = avformat_write_header(vp->fmt_ctx, &dict);
		if (ret < 0)
		{
			LOG_PRINT("%s....avformat_write_header failed", __FUNCTION__);
			ERR_NULL_EXIT
		}

		LOG_PRINT("%s....Create an encode thread", __FUNCTION__);
		/* 创建编码线程 */
		pthread_create(&vp->pp->tid, NULL, encode_thread, vp);
		//线程退出时自动释放资源
		pthread_detach(vp->pp->tid);
		return vp;
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		LOG_PRINT("%s error\n", __FUNCTION__);
	}
}

codecCtx_fill:

static void codecCtx_fill(AVCodecContext *codec_ctx, AVDictionary **dict, const char *out_fmt)
{
	int ret;
	/* 输出格式是mov(透明通道视频)*/
	if (!strcmp("alpha_mov", out_fmt)) {
		codec_ctx->pix_fmt = AV_PIX_FMT_ARGB;
	}
	else {
		codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	}
	if (!strcmp(out_fmt, "mp4") || !strcmp(out_fmt, "mkv") || !strcmp(out_fmt, "mov"))
	{
		codec_ctx->codec_id = AV_CODEC_ID_H264;
		codec_ctx->bit_rate = 800 * 1000;

		codec_ctx->rc_max_rate = 800 * 1000;
		codec_ctx->rc_buffer_size = 500 * 1000;
		/* 设置图像组层的大小, gop_size越大,文件越小 */
		codec_ctx->gop_size = 30;
		codec_ctx->max_b_frames = 3;
		/* 设置h264中相关的参数 */
		codec_ctx->qmin = 10;	//2
		codec_ctx->qmax = 31;	//31
		codec_ctx->max_qdiff = 4;
		codec_ctx->me_range = 16;	//0	
		codec_ctx->max_qdiff = 4;	//3	
		codec_ctx->qcompress = 0.6;	//0.5
		ret = av_dict_set(dict, "profile", "high", 0);
		// 通过--preset的参数调节编码速度和质量的平衡。
		ret = av_dict_set(dict, "preset", "superfast", 0);
		ret = av_dict_set(dict, "threads", "0", 0);
		ret = av_dict_set(dict, "crf", "26", 0);
		// zerolatency: 零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码
		ret = av_dict_set(dict, "tune", "zerolatency", 0);
		return;
	}
	else {
		codec_ctx->bit_rate = 4096 * 1000;
	}

	if (!strcmp(out_fmt, "avi")) {
		codec_ctx->codec_id = AV_CODEC_ID_MPEG4;
	}
	//未确定是V3
	else if (!strcmp(out_fmt, "wmv")) {
		codec_ctx->codec_id = AV_CODEC_ID_MSMPEG4V3;
	}
	else if (!strcmp(out_fmt, "flv")) {
		codec_ctx->codec_id = AV_CODEC_ID_FLV1;
	}
	else if (!strcmp(out_fmt, "alpha_mov")) {
		codec_ctx->codec_id = AV_CODEC_ID_QTRLE;
	}
	/* 未找到对应格式时的默认选择 */
	else {
		codec_ctx->codec_id = AV_CODEC_ID_MPEG4;
	}
}

frame_buf_alloc:

static int frame_buf_alloc(videoParm *vp, int width, int height)
{
	int raw_pix_fmt = AV_PIX_FMT_BGRA;
	/* 分配ARGB帧内存 */
	vp->argb_frame = av_frame_alloc();
	if (vp->argb_frame == NULL)
	{
		LOG_PRINT("%s....argb_frame alloc failed", __FUNCTION__);
		return -1;
	}
	/* 如果不是透明通道,则分配YUV帧内存 */
	if (strcmp(vp->format, "alpha_mov")) {
		vp->yuv_frame = av_frame_alloc();
		if (vp->yuv_frame == NULL) {
			LOG_PRINT("%s....yuv_frame alloc failed", __FUNCTION__);
			return -1;
		}
	}
	//av_frame_get_buffer
	vp->argb_size = av_image_get_buffer_size((AVPixelFormat)raw_pix_fmt, width, height, 1);
	/* 为ARGB帧的data buffers分配内存 */
	vp->argb_buf = (uint8_t*)av_malloc(vp->argb_size);

	/* 如果不是透明通道,则为YUV帧的data buffers分配内存 */
	if (strcmp(vp->format, "alpha_mov")) {
		vp->yuv_size = av_image_get_buffer_size(vp->codec_ctx->pix_fmt, width, height, 1);
		vp->yuv_buf = (uint8_t*)av_malloc(vp->yuv_size);
	}

	//先让data指针和linesize指针指向buf,后面再从文件中写入数据到buf
	/* 填充argb_frame */
	av_image_fill_arrays(vp->argb_frame->data, vp->argb_frame->linesize, vp->argb_buf, (AVPixelFormat)raw_pix_fmt, vp->width, vp->height, 1);
	/* 如果不是透明通道,则填充yuv_frame */
	if (strcmp(vp->format, "alpha_mov")) {
		av_image_fill_arrays(vp->yuv_frame->data, vp->yuv_frame->linesize, vp->yuv_buf, vp->codec_ctx->pix_fmt, width, height, 1);
	}
	/* 如果不是透明通道,获取SwsContext */
	//SWS_FAST_BILINEAR  SWS_BICUBIC
	if (strcmp(vp->format, "alpha_mov")) {
		vp->sws_ctx = sws_getContext(width, height, (AVPixelFormat)raw_pix_fmt, width, height, vp->codec_ctx->pix_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL);
	}
	return 0;
}

BGRA2ARGB:

/*
* 外部传来的是BGRA帧,而AV_CODEC_ID_QTRLE这个编码器不支持AV_PIX_FMT_BGRA,所以需要转为ARGB帧
*/
void BGRA2ARGB(videoParm *vp)
{
	char B1, G1, R1, A1, B2, G2, R2, A2;
	int cnt = vp->argb_size / 8;
	uint8_t *p = vp->argb_buf;
	for (int i = 0; i < cnt; ++i) {
		B1 = *p;
		G1 = *(p + 1);
		R1 = *(p + 2);
		A1 = *(p + 3);
		B2 = *(p + 4);
		G2 = *(p + 5);
		R2 = *(p + 6);
		A2 = *(p + 7);
		*p = A1;
		*(p + 1) = R1;
		*(p + 2) = G1;
		*(p + 3) = B1;
		*(p + 4) = A2;
		*(p + 5) = R2;
		*(p + 6) = G2;
		*(p + 7) = B2;
		p += 8;
	}
	/* 如果argb_size多4个字节 */
	if (vp->argb_size % 8) {
		//p += 8;
		B1 = *p;
		G1 = *(p + 1);
		R1 = *(p + 2);
		A1 = *(p + 3);
		*p = A1;
		*(p + 1) = R1;
		*(p + 2) = G1;
		*(p + 3) = B1;
	}
}

vertical_flip:

/*
* 外部传进来的是倒立的帧,需要进行翻转(BGRA转ARGB)
*/
int vertical_flip(videoParm *vp)
{
	size_t w = vp->width * 4;
	size_t h = vp->height;
	size_t m = h / 2;
	uint8_t *top = vp->argb_buf;
	uint8_t *bottom = vp->argb_buf + w * (h - 1);
	uint8_t *tmp_buf = (uint8_t*)av_malloc(w);
	if (NULL == tmp_buf) {
		LOG_PRINT("%s....av_malloc failed", __FUNCTION__);
		return -1;
	}

	for (int i = 0; i < m; ++i) {
		memcpy(tmp_buf, top, w);
		memcpy(top, bottom, w);
		memcpy(bottom, tmp_buf, w);
		top += w;
		bottom -= w;
	}
	if (tmp_buf) {
		av_free(tmp_buf);
	}
	return 0;
}

pushing_frame:

int pushing_frame(void *rawdatabuf, void *videoparam)
{
	__try {
		int ret = -1;
		int ret_size = -1;
		//int space = -1;
		bool exit_flag = FALSE;
		videoParm *vp = (videoParm*)videoparam;

		/* 如果fifo可读空间>=10帧,停止push */
		pthread_mutex_lock(&vp->pp->mutex_t);
		if ((ret_size = av_fifo_size(vp->fifo_buf)) >= (vp->argb_size * 10)) {
			exit_flag = TRUE;
		}
		pthread_mutex_unlock(&vp->pp->mutex_t);
		if (exit_flag) {
			LOG_PRINT("%s....Wait for a minute to pushing_frame!!!", __FUNCTION__);
			return 0;
		}

		/* AVFifoBuf不到10帧,因此向其中写入帧 */
		pthread_mutex_lock(&vp->pp->mutex_t);
		ret = av_fifo_generic_write(vp->fifo_buf, rawdatabuf, vp->argb_size, NULL);
		pthread_mutex_unlock(&vp->pp->mutex_t);
		
		//signal好像不用加锁
		pthread_cond_signal(&vp->pp->cond_t);

#ifdef DEBUG_
		++g_push_cnt;
#endif
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		LOG_PRINT("%s error\n", __FUNCTION__);
	}
	return 1;
}

encode_thread:

static void* encode_thread(void *videoparm)
{
	__try {

		int ret = -1;
		bool flag;
		bool if_finished = FALSE;
		int status = -1;
		videoParm *vp = (videoParm*)videoparm;

		if (videoparm == NULL) {
			LOG_PRINT("%s....videoparm is NULL", __FUNCTION__);
			callback_function((EncodeCallbackFunction)vp->notification, vp, status, 0);
			ERR_NULL_EXIT
		}
		while (1) {
			flag = FALSE;
			pthread_mutex_lock(&vp->pp->mutex_t);
			/* 手动停止 */
			if (vp->is_stop) {
				flag = TRUE;
				status = 1;
			}
			/* 外部没有帧了 */
			if (vp->is_finished) {
				if_finished = TRUE;
				status = 0;
			}
			pthread_mutex_unlock(&vp->pp->mutex_t);
			if (flag || if_finished) {
				break;
			}
			/* 如果fifo_buf中没有帧可以读,那就Sleep */
			pthread_mutex_lock(&vp->pp->mutex_t);
			if (av_fifo_size(vp->fifo_buf) < vp->argb_size) {
				flag = TRUE;
			}
			else {
				flag = FALSE;
			}
			pthread_mutex_unlock(&vp->pp->mutex_t);
			if (flag) {
				LOG_PRINT("%s....Sleep 1 s", __FUNCTION__);
				encode_delay_time(&vp->pp->mutex_t, &vp->pp->cond_t, 1000);
				continue;
			}
			pthread_mutex_lock(&vp->pp->mutex_t);
			av_fifo_generic_read(vp->fifo_buf, vp->argb_buf, vp->argb_size, NULL);
			int size = av_fifo_size(vp->fifo_buf);
			pthread_mutex_unlock(&vp->pp->mutex_t);

#ifdef DEBUG_
			DWORD dwStart = timeGetTime();
#endif
			/* 垂直翻转图像 */
			/**/
			if (vertical_flip(vp)) {
				ERR_NULL_EXIT
			}

#ifdef DEBUG_
			DWORD dwEnd = timeGetTime();
			printf("The duration is %lu milliseconds\n", dwEnd - dwStart);
#endif

			if (!strcmp("alpha_mov", vp->format)) {
				BGRA2ARGB(vp);
			}

			/* 如果不是透明通道,RGB帧转YUV帧 */
			if (vp->yuv_frame != NULL) {
				sws_scale(vp->sws_ctx, vp->argb_frame->data, vp->argb_frame->linesize, 0, vp->height, vp->yuv_frame->data, vp->yuv_frame->linesize);	
				vp->yuv_frame->pts = vp->current_count * (vp->stream->time_base.den) / ((vp->stream->time_base.num) * vp->fps);	
				vp->yuv_frame->format = vp->codec_ctx->pix_fmt;
				vp->yuv_frame->width = vp->width;
				vp->yuv_frame->height = vp->height;
				++vp->current_count;
			}
			else {
				vp->argb_frame->pts = vp->current_count * (vp->stream->time_base.den) / ((vp->stream->time_base.num) * vp->fps);
				vp->argb_frame->format = vp->codec_ctx->pix_fmt;
				vp->argb_frame->width = vp->width;
				vp->argb_frame->height = vp->height;
				++vp->current_count;
			}

			int send_flag = -1, recv_flag = -1;
			/* 如果不是透明通道,为packet的data buffer 分配内存 */

			if (vp->yuv_size != 0) {
				av_new_packet(vp->pkt, vp->yuv_size);
			}
			else {
				av_new_packet(vp->pkt, vp->argb_size);
			}

			/* 如果不是透明通道 */
			if (vp->yuv_frame != NULL) {
				send_flag = avcodec_send_frame(vp->codec_ctx, vp->yuv_frame);
			}
			else {
				send_flag = avcodec_send_frame(vp->codec_ctx, vp->argb_frame);	
			}
			if (send_flag < 0) {
				LOG_PRINT("%s....Avcodec_send_frame failed", __FUNCTION__);
			}
			recv_flag = avcodec_receive_packet(vp->codec_ctx, vp->pkt);
			if (recv_flag < 0) {
				LOG_PRINT("%s....avcodec_receive_packet failed", __FUNCTION__);
			}
			if (send_flag != 0 || recv_flag != 0) {
				av_packet_unref(vp->pkt);
				continue;
			}

			vp->pkt->stream_index = vp->stream->index;
			/* 将帧包写入输出文件 */
			ret = av_write_frame(vp->fmt_ctx, vp->pkt);	
			/* 将packet的值还原为默认值 */
			av_packet_unref(vp->pkt);
			if (ret < 0) {
				LOG_PRINT("%s....av_write_frame failed", __FUNCTION__);
				ERR_NULL_EXIT
			}
			else if (ret == 0) {
#ifdef DEBUG_
				++g_write_frame_cnt;
#endif
			}

			encode_delay_time(&vp->pp->mutex_t, &vp->pp->cond_t, 10);
		}

#ifdef DEBUG_
		LOG_PRINT("%s....av_write_frame succeed %d times", __FUNCTION__, g_write_frame_cnt);
#endif

		/*
		* end_push后将fifo buf中剩余的帧写入文件
		*/
		while (1) {
			flag = FALSE;
			pthread_mutex_lock(&vp->pp->mutex_t);
			if (vp->is_stop) {
				flag = TRUE;
			}
			pthread_mutex_unlock(&vp->pp->mutex_t);
			if (flag) {
				break;
			}
			pthread_mutex_lock(&vp->pp->mutex_t);
			if ((ret = av_fifo_size(vp->fifo_buf)) < vp->argb_size) {
				flag = FALSE;
			}
			else {
				flag = TRUE;
			}
			pthread_mutex_unlock(&vp->pp->mutex_t);
			if (!flag) {
				break;
			}
			else {
				//编码下一帧
				if (encode_remaining(vp)) {
					ERR_NULL_EXIT
				}
			}
		}

#ifdef DEBUG_
		LOG_PRINT("%s....encode_remaining, av_write_frame succeed %d times", __FUNCTION__, g_write_frame_cnt);
#endif

		pthread_mutex_lock(&vp->pp->mutex_t);
		if (vp->is_stop) {
			flag = TRUE;
		}
		else {
			flag = FALSE;
		}
		pthread_mutex_unlock(&vp->pp->mutex_t);
		if (!flag) {
			/* 如果没有push_frame过,就不flush */
			if (ret != -1) {
				flush_encode(vp);
			}
		}

#ifdef DEBUG_
		LOG_PRINT("%s....flush_encode, av_write_frame succeed %d times", __FUNCTION__, g_write_frame_cnt);
#endif

		/* 将流尾写入输出文件并释放文件 */
		av_write_trailer(vp->fmt_ctx);

		callback_function((EncodeCallbackFunction)vp->notification, vp, status, 0);
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		LOG_PRINT("%s error\n", __FUNCTION__);
	}
	return NULL;
}

encode_remaining:

static int encode_remaining(videoParm *vp)
{
	static int s_frame_cnt = 1;
	pthread_mutex_lock(&vp->pp->mutex_t);
	av_fifo_generic_read(vp->fifo_buf, vp->argb_buf, vp->argb_size, NULL);
	pthread_mutex_unlock(&vp->pp->mutex_t);

	/* 垂直翻转图像 */
	if (vertical_flip(vp)) {
		return -1;
	}
	if (!strcmp("alpha_mov", vp->format)) {
		BGRA2ARGB(vp);
	}

	/* RGB帧转YUV帧 */
	/* 如果不是透明通道 */
	if (vp->yuv_frame != NULL) {
		sws_scale(vp->sws_ctx, vp->argb_frame->data, vp->argb_frame->linesize, 0, vp->height, vp->yuv_frame->data, vp->yuv_frame->linesize);	

		vp->yuv_frame->pts = vp->current_count * (vp->stream->time_base.den) / ((vp->stream->time_base.num) * vp->fps);	
		//vp->yuv_frame->pts = vp->current_count * vp->codec_ctx->time_base.den / (vp->codec_ctx->time_base.num * vp->fps);
		vp->yuv_frame->format = vp->codec_ctx->pix_fmt;
		vp->yuv_frame->width = vp->width;
		vp->yuv_frame->height = vp->height;
		++vp->current_count;
	}
	else {
		vp->argb_frame->pts = vp->current_count * (vp->stream->time_base.den) / ((vp->stream->time_base.num) * vp->fps);	
		//vp->argb_frame->pts = vp->current_count * vp->codec_ctx->time_base.den / (vp->codec_ctx->time_base.num * vp->fps);
		vp->argb_frame->format = vp->codec_ctx->pix_fmt;
		vp->argb_frame->width = vp->width;
		vp->argb_frame->height = vp->height;
		++vp->current_count;
	}
	int send_flag = -1, recv_flag = -1;
	/* 为packet的data buffer 分配内存 */
	if (vp->yuv_size != 0) {
		av_new_packet(vp->pkt, vp->yuv_size);
	}
	else {
		av_new_packet(vp->pkt, vp->argb_size);
	}

	/* 如果不是透明通道 */
	if (vp->yuv_frame != NULL) {
		send_flag = avcodec_send_frame(vp->codec_ctx, vp->yuv_frame);
	}
	else {
		send_flag = avcodec_send_frame(vp->codec_ctx, vp->argb_frame);	//46ms,49ms,152ms
	}
	if (send_flag < 0) {
		LOG_PRINT("%s....Avcodec_send_frame failed", __FUNCTION__);
	}
	recv_flag = avcodec_receive_packet(vp->codec_ctx, vp->pkt);
	//编码失败不写入文件,跳到下一帧
	if (send_flag != 0 || recv_flag != 0) {
		av_packet_unref(vp->pkt);
		return 0;
	}
	vp->pkt->stream_index = vp->stream->index;
	/* 将帧包写入输出文件 */
	int ret = av_write_frame(vp->fmt_ctx, vp->pkt);	//21ms,8ms
	if (ret < 0) {
		LOG_PRINT("%s....av_write_frame failed", __FUNCTION__);
	}
	else if (ret == 0) {
#ifdef DEBUG_
		++g_write_frame_cnt;
		printf("fifo av_write_frame succeed %3d times\n", s_frame_cnt++);
#endif
		LOG_PRINT("%s....fifo av_write_frame succeed", __FUNCTION__);
	}
	/* 将packet的值还原为默认值 */
	av_packet_unref(vp->pkt);
	return 0;
}

flush_encode:

static void flush_encode(videoParm *vp)
{
#ifdef DEBUG_
	LOG_PRINT("%s....g_push_cnt = %d", __FUNCTION__, g_push_cnt);
#endif

	int flush_frame_cnt = 1;
	int ret = avcodec_send_frame(vp->codec_ctx, NULL);
	while (1) {
		av_new_packet(vp->pkt, vp->argb_size);
		vp->pkt->data = NULL;
		vp->pkt->size = 0;
		ret = avcodec_receive_packet(vp->codec_ctx, vp->pkt);
		if (ret == AVERROR_EOF) {
			break;	//break之后忘记了av_packet_unref,参考encode_audio_frame_flush
		}
		else if (ret == 0) {
			vp->pkt->stream_index = vp->stream->index;
			ret = av_write_frame(vp->fmt_ctx, vp->pkt);	//21ms,8ms
			if (ret == 0) {
#ifdef DEBUG_
				++g_write_frame_cnt;
				printf("flush frame succeed %3d times\n", flush_frame_cnt++);
#endif
				LOG_PRINT("%s....flush frame succeed", __FUNCTION__);
			}
			else if (ret < 0) {
				LOG_PRINT("%s....av_write_frame failed", __FUNCTION__);
			}
			else {	//1
				LOG_PRINT("%s....flushed and there is no more data to flush", __FUNCTION__);
			}
		}
		av_packet_unref(vp->pkt);

		Sleep(10);
	}
	av_packet_unref(vp->pkt);
}

ending_push:

void ending_push(void *videoparam)
{
	videoParm *vp = (videoParm*)videoparam;
	pthread_mutex_lock(&vp->pp->mutex_t);
	vp->is_finished = TRUE;
	pthread_mutex_unlock(&vp->pp->mutex_t);
}

releasing:

void releasing(videoParm *vp)
{
	__try {
		int err;
		/* 释放锁 */
		pthread_mutex_destroy(&vp->pp->mutex_t);
		pthread_cond_destroy(&vp->pp->cond_t);
		if (vp->format) {
			free((void*)vp->format);
		}
		if (vp->path) {
			free((void*)vp->path);
		}

		/* 释放Sws_Context结构体 */
		if (vp->sws_ctx != NULL) {
			sws_freeContext(vp->sws_ctx);
		}
		/* 释放AVFifoBuffer结构体 */
		av_fifo_free(vp->fifo_buf);
		/* 释放ARGB帧和YUV帧结构 */
		av_frame_free(&vp->argb_frame);
		if (vp->yuv_frame != NULL) {
			av_frame_free(&vp->yuv_frame);
		}
		/* 释放ARGB帧和YUV帧内存 */
		av_free(vp->argb_buf);
		if (vp->yuv_buf != NULL) {
			av_free(vp->yuv_buf);
		}
		/* 释放packet结构体*/
		av_packet_free(&vp->pkt);
		/* 释放AVCodecContext结构体 */
		avcodec_free_context(&vp->codec_ctx);
		//avcodec_close(vp->codec_ctx);

		/* 关闭AVIOContext */
		avio_close(vp->fmt_ctx->pb);
		/* 必须先调用avio_close关闭了与输出文件的关联,才能再调用remove删除输出文件 */
		if (vp->tmp_path) {
#ifdef REMOVE_TEMP_FILE
			err = remove(vp->tmp_path);
			if (!err) {
				LOG_PRINT("%s....Remove temp file succeed", __FUNCTION__);
			}
			else {
				DWORD tt = GetLastError();
				LOG_PRINT("%s....Remove temp file failed, error code is %d", __FUNCTION__, err);
			}
#endif
			free(vp->tmp_path);
		}
		/* 释放AVFormatContext结构体 */
		avformat_free_context(vp->fmt_ctx);
		vp->fmt_ctx = NULL;

		/* 释放videoParm结构体 */
		av_free(vp);
		/* 设为NULL,避免使用野指针 */
		vp = NULL;
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		LOG_PRINT("%s error\n", __FUNCTION__);
	}
}

setting_stop:

void setting_stop(void *videoparm)
{
	videoParm *vp = (videoParm*)(videoparm);
	pthread_mutex_lock(&vp->pp->mutex_t);
	vp->is_stop = TRUE;
	pthread_mutex_unlock(&vp->pp->mutex_t);
}

callback_function:

static void callback_function(EncodeCallbackFunction fun, void *identifier, int status, int percent)
{
	if (fun != NULL) {
		fun(identifier, status, percent);
	}
}

EncodeCreateTempFile:

/* ANSI */
static char* EncodeCreateTempFile(const char *extention)
{
	const char *ext;
	if (!strcmp("alpha_mov", extention))
		ext = "mov";
	else
		ext = extention;
	char *path = EncodeCreateTempDir("EnocdeTemp");
	if (path == NULL) {
		LOG_PRINT("%s....Create temp file failed", __FUNCTION__);
		return NULL;
	}

	char *filename = (char*)malloc(1024);
	memset(filename, 0, 1024);
	sprintf(filename, "%s/ENC%lld.%s", path, (int64_t)av_gettime(), ext);
	LOG_PRINT("%s....The temp file in path: %s", __FUNCTION__, filename);
	free(path);

	return filename;
}

encode_delay_time:

static void encode_delay_time(pthread_mutex_t *p_mutex, pthread_cond_t *p_cond, uint64_t ms)
{
#ifndef ETIMEDOUT
#define	ETIMEDOUT	60
#endif
	struct timespec m_time;
	int64_t tmp_us = av_gettime(); //usec    
	uint64_t current_us = tmp_us;
	if (tmp_us<0)
	{
		current_us = (tmp_us - 1) ^ ((int64_t)(-1));
	}
	current_us += ms * 1000;
	m_time.tv_sec = current_us / 1000000;
	m_time.tv_nsec = (current_us % 1000000) * 1000;
	pthread_mutex_lock(p_mutex);
	int res = pthread_cond_timedwait(p_cond, p_mutex, (const struct timespec *)&m_time);
	pthread_mutex_unlock(p_mutex);
	//sleep(1);
	if (res == ETIMEDOUT)//timeout
	{
		/* 超时 */
	}
	else
	{
		/* 被信号唤醒 */
	}
}

EncodeCreateTempDir:

/* ANSI */
static char *EncodeCreateTempDir(char *sub_dir)
{
	char *path = (char *)malloc(1024);
	memset(path, 0, 1024);
#ifdef _WIN32
	char *tmp_path = getenv("TEMP");
#else
	char *tmp_path = getenv("TMPDIR");
#endif
	sprintf(path, "%s", tmp_path);
	if (strlen(path) == 0) {
		free(path);
		return NULL;
	}
	if (sub_dir&&strlen(sub_dir))
	{
		char chr = path[strlen(path) - 1];
		if (chr != '/'&&chr != '\\') {
			memcpy(path + strlen(path), "/", 1);
		}
		char *tmp = NULL;
		while ((tmp = strrchr(path, '\\')))
		{
			*tmp = '/';
		}
		memcpy(path + strlen(path), sub_dir, strlen(sub_dir));
	}
	if (access(path, 0)<0) {
#ifdef _WIN32
		int ret = mkdir(path);
		if (ret<0)
		{
			LOG_PRINT("%s....mkdir failed", __FUNCTION__);
			printf("failed to create tmp!\n");
		}
#else
		char command[1024] = { 0 };
		sprintf(command, "mkdir %s", path);
		system(command);
#endif

	}
	if (access(path, 0)<0) {
		//failed
		LOG_PRINT("%s....access failed", __FUNCTION__);
		free(path);
		return NULL;
	}
	return path;
}

如果不需要与音频重复用(remux)则传入audio_exist = 0,如果需要将视频文件和音频文件合成,参考将视频与音频重复用(remux)成新的视频

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值