前言
开发库的版本ffmpeg3.2、sdl2.0、Qt5.12.7,在window下利用mingwin64编译。前面介绍了简易播放器的开发,这次是在前面的基础上,实现实时将解码的YUV数据编码成mp4文件存储。《最简单的FFmpeg+SDL+Qt视频播放器-播放MP4文件_ALANRUOMENG的博客-CSDN博客》
流程
代码
#include <QCoreApplication>
#ifdef __cplusplus
#define __STDC_CONSTANT_MACROS
#ifdef _STDINT_H
#undef _STDINT_H
#endif
# include <stdint.h>
#endif
#define OUTPUT_YUV420P 0
extern "C"
{
#include"libavcodec/avcodec.h"
#include"libavformat/avformat.h"
#include"libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include"libavutil/opt.h"
#include"SDL.h"
}
#undef main
#define SFM_REFRESH_EVENT (SDL_USEREVENT+1)
#define SFM_BREAK_EVENT (SDL_USEREVENT+2)
int thread_exit=0;
int thread_pause=0;
int sfp_refresh_thread(void * opaque){
thread_exit=0;
thread_pause=0;
while (!thread_exit) {
if(!thread_pause)
{
SDL_Event event;
event.type=SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
}
SDL_Delay(40);
}
thread_exit=0;
thread_pause=0;
SDL_Event event;
event.type=SFM_BREAK_EVENT;
SDL_PushEvent(&event);
return 0;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
AVFormatContext *pFormatCtx;
int i,videoindex;
AVCodecContext *pCodecCtx;
AVCodec * pCodec;
AVFrame * pFrame,*pFrameYUV;
uint8_t *out_buffer;
AVPacket * packet;
struct SwsContext *img_convert_ctx;
//输出编码
AVFormatContext *pFormatCtx_out;
AVCodecContext *pCodecCtx_out;
AVCodec *pCodec_out;
int iout,got_output;
AVStream *outstream;
FILE * fp_out;
AVFrame *pFrameOut;
AVPacket packetout;
int framecnt=0;
int out_w=100,out_h=100;//输出视频宽度
int screen_w,screen_h;
SDL_Window *screen;
SDL_Renderer*sdlRenderer;
SDL_Texture *sdlTexture;
SDL_Rect sdlRect;
SDL_Thread* video_tid;
SDL_Event event;
FILE* fp_yuv;
int y_size;
int ret,got_picture;
int y_sizeout;
char filepath[]="d://test.mp4";
#if TEST_HEVC
AVCodecID codec_id=AV_CODEC_ID_HEVC;
char filename_out[]="ds.hevc";
#else
AVCodecID codec_id=AV_CODEC_ID_H264;
char filename_out[]="test.mp4";
#endif
av_register_all();
avcodec_register_all();//注册编解码器
avformat_network_init();
pFormatCtx=avformat_alloc_context();
if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0)
{
printf("无法打开信息流");
return -1;
}
if(avformat_find_stream_info(pFormatCtx,NULL)<0)
{
printf("无法查找到流信息");
return -1;
}
videoindex=-1;
for(i=0;i<pFormatCtx->nb_streams;i++)
{
if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
{
videoindex=i;
break;
}
}
if(videoindex<0)
{
printf("没有视频流");
return -1;
}
pCodecCtx=avcodec_alloc_context3(NULL);
if(!pCodecCtx)
{
printf("分配解码器上下文内存失败");
return -1;
}
//获取编解码器
if(avcodec_parameters_to_context(pCodecCtx,pFormatCtx->streams[videoindex]->codecpar)<0)
{
printf("拷贝视频流解码器参数到解码器对象失败");
return -1;
}
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(!pCodec)
{
printf("查找解码器失败");
return -1;
}
//打开解码器
if(avcodec_open2(pCodecCtx,pCodec,NULL)<0)
{
printf("打开解码器失败");
return -1;
}
printf("------------------------------视频信息-----------------------\n");
// av_dump_format(pFormatCtx,0,filepath,0);
printf("------------------------------视频信息-----------------------\n");
//*****************视频编码MP4操作*******start*******//
out_w=pCodecCtx->width;
out_h=pCodecCtx->height;
//寻找支持的编码器
avformat_alloc_output_context2(&pFormatCtx_out,NULL,0,filename_out);//创建编码器格式上下文
//打开输出文件
if (avio_open(&pFormatCtx_out->pb, filename_out, AVIO_FLAG_WRITE) < 0) {
return -1;
}
pCodec_out=avcodec_find_encoder(codec_id);
if(!pCodec_out)
{
printf("不支持的编码器");
return -1;
}
//根据找到的编码器创建上下文
pCodecCtx_out=avcodec_alloc_context3(pCodec_out);
if(!pCodecCtx_out)
{
printf("分配编码器上下文内存失败");
return -1;
}
//设置编码器上下文参数
pCodecCtx_out->bit_rate=200000;
pCodecCtx_out->width=out_w;
pCodecCtx_out->height=out_h;
pCodecCtx_out->time_base.num=1;//分子
pCodecCtx_out->time_base.den=25;//分母帧率
pCodecCtx_out->gop_size=10;
pCodecCtx_out->max_b_frames=1;
pCodecCtx_out->pix_fmt=AV_PIX_FMT_YUV420P;
if(codec_id==AV_CODEC_ID_H264)
{
av_opt_set(pCodecCtx_out->priv_data, "tune", "zerolatency", 0);
}
//打开编码器
if(avcodec_open2(pCodecCtx_out,pCodec_out,NULL)<0)
{
printf("打开编码器上下文失败");
return -1;
}
//建立一个流对象,用于将数据传递进去
outstream=avformat_new_stream(pFormatCtx_out,NULL);
outstream->id=0;
outstream->codecpar->codec_tag=0;
outstream->time_base=pCodecCtx_out->time_base;
//将编码器上下文中的参数传递给流中
avcodec_parameters_from_context(outstream->codecpar,pCodecCtx_out);
y_sizeout=pCodecCtx_out->width*pCodecCtx_out->height;
ret = avformat_write_header(pFormatCtx_out, NULL);
if (ret < 0)
{
printf( " avformat_write_header failed!" );
return -1;
}
//解码帧数据存放,内存分配和相关参数设置
int buffer_size = av_image_get_buffer_size(pCodecCtx_out->pix_fmt,pCodecCtx_out->width,pCodecCtx_out->height,1);
uint8_t *outbuffer = (uint8_t *)av_malloc(buffer_size);
//到此将编码器都设置好了。
pFrameOut=av_frame_alloc();
if(!pFrameOut)
{
printf("失败");
return -1;
}
av_image_fill_arrays(pFrameOut->data,pFrameOut->linesize, outbuffer, pCodecCtx_out->pix_fmt,pCodecCtx_out->width,pCodecCtx_out->height,1);
pFrameOut->width=pCodecCtx_out->width;
pFrameOut->height=pCodecCtx_out->height;
pFrameOut->format=pCodecCtx_out->pix_fmt;
av_init_packet(&packetout);//初始化输出包
//*****************视频编码MP4操作******end********//
pFrame=av_frame_alloc();
pFrameYUV=av_frame_alloc();
out_buffer=(uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height,1));
av_image_fill_arrays(pFrameYUV->data,pFrameYUV->linesize,out_buffer,AV_PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height,1);
packet=(AVPacket*)av_malloc(sizeof (AVPacket));
img_convert_ctx=sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,
pCodecCtx->width,pCodecCtx->height,AV_PIX_FMT_YUV420P,SWS_BICUBIC,NULL,NULL,NULL);
#if OUTPUT_YUV420P
fp_yuv=fopen("output.yuv","wb+");
#endif
if(SDL_Init(SDL_INIT_VIDEO| SDL_INIT_AUDIO|SDL_INIT_TIMER))
{
printf("无法初始化SDL");
return -1;
}
screen_w=pCodecCtx->width;
screen_h=pCodecCtx->height;
screen=SDL_CreateWindow("播放器",SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,screen_w,screen_h,SDL_WINDOW_OPENGL);
if(!screen)
{
printf("创建播放窗口失败");
return -1;
}
sdlRenderer=SDL_CreateRenderer(screen,-1,0);
sdlTexture=SDL_CreateTexture(sdlRenderer,SDL_PIXELFORMAT_IYUV,SDL_TEXTUREACCESS_STREAMING,screen_w,screen_h);
sdlRect.x=0;
sdlRect.y=0;
sdlRect.w=screen_w;
sdlRect.h=screen_h;
video_tid=SDL_CreateThread(sfp_refresh_thread,NULL,NULL);
/*
/测试本地YUV数据转存MP4数据--start/
// uint8_t * file_buffer = (uint8_t *)av_malloc(out_w * out_h * 3 / 2);
// FILE *in_file = fopen("d:\\output.yuv", "rb");
// if(!in_file)
// {
// printf("打开输入失败");
// return -1;
// }
// while (true) {
// //读取yuv帧数据 注意yuv420p的长度 width * height * 3 / 2
// if (fread(file_buffer, 1, out_w * out_h * 3 / 2, in_file) <= 0)
// {
// break;
// } else if (feof(in_file)) {
// break;
// }
// //封装yuv帧数据
// pFrameOut->data[0] = file_buffer;//y数据的起始位置在数组中的索引
// pFrameOut->data[1] = file_buffer + out_w * out_h;//u数据的起始位置在数组中的索引
// pFrameOut->data[2] = file_buffer + out_w * out_h * 5 / 4;//v数据的起始位置在数组中的索引
// pFrameOut->linesize[0] = out_w;//y数据的行宽
// pFrameOut->linesize[1] = out_w / 2;//u数据的行宽
// pFrameOut->linesize[2] = out_w / 2;//v数据的行宽
// pFrameOut->pts = framecnt;
// framecnt++;
// avcodec_send_frame(pCodecCtx_out, pFrameOut);//将yuv帧数据送入编码器
// while(true) {
// int ret = avcodec_receive_packet(pCodecCtx_out, &packetout);//从编码器中取出h264帧
// if (ret) {
// av_packet_unref(&packetout);
// break;
// }
// printf("frame id:%d\n",framecnt);
// av_packet_rescale_ts(&packetout, pCodecCtx_out->time_base, outstream->time_base);
// packetout.stream_index = outstream->index;
// //将帧写入视频文件中,与av_write_frame的区别是,将对 packet 进行缓存和 pts 检查。
// av_interleaved_write_frame(pFormatCtx_out, &packetout);
// }
// }
// av_write_trailer(pFormatCtx_out);
/测试本地YUV数据转存MP4数据--end/
*/
for(;;)
{
SDL_WaitEvent(&event);
if(event.type==SFM_REFRESH_EVENT)
{
while (1) {
if(av_read_frame(pFormatCtx,packet)<0)
thread_exit=1;
if(packet->stream_index==videoindex)
break;
}
ret=avcodec_send_packet(pCodecCtx,packet);
if(ret<0)
{
printf("发送失败");
}
while (ret>=0) {
ret=avcodec_receive_frame(pCodecCtx,pFrame);
if(ret==AVERROR(EAGAIN)||ret==AVERROR_EOF)
{
break;
}
else if(ret<0)
{
//释放资源
// av_frame_unref(pFrame);
// av_frame_unref(pFrameYUV);
}
if(ret>=0)
{
// framecnt++;
sws_scale(img_convert_ctx,pFrame->data,pFrame->linesize,0,pCodecCtx->height,pFrameYUV->data,pFrameYUV->linesize);
// av_frame_ref(pFrameOut,pFrameYUV);
/*************将解码的Frame数据发送出去用于编码******start***************/
pFrameOut->data[0]=pFrameYUV->data[0];
pFrameOut->data[1]=pFrameYUV->data[1];
pFrameOut->data[2]=pFrameYUV->data[2];
pFrameOut->pts=framecnt;//记录时戳
framecnt++;
//将数据发送给编码上下文
ret=avcodec_send_frame(pCodecCtx_out,pFrameOut);
/*************将解码的Frame数据发送出去用于编码*******end**************/
#if OUTPUT_YUV420P
y_size=pCodecCtx->width*pCodecCtx->height;
fwrite(pFrameOut->data[0],1,y_size,fp_yuv); //Y
fwrite(pFrameOut->data[1],1,y_size/4,fp_yuv); //U
fwrite(pFrameOut->data[2],1,y_size/4,fp_yuv); //V
#endif
#if 0
SDL_UpdateTexture(sdlTexture,NULL,pFrameYUV->data[0],pFrameYUV->linesize[0]);
#else
SDL_UpdateYUVTexture(sdlTexture,&sdlRect,pFrameYUV->data[0],pFrameYUV->linesize[0],pFrameYUV->data[1],pFrameYUV->linesize[1],pFrameYUV->data[2],pFrameYUV->linesize[2]);
#endif
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer,sdlTexture,NULL,&sdlRect);
SDL_RenderPresent(sdlRenderer);
/*************************接收YUV帧数据进行编码并写入文件********start**********************************/
while(true) {
int ret = avcodec_receive_packet(pCodecCtx_out, &packetout);//从编码器中取出h264帧
if (ret) {
av_packet_unref(&packetout);
break;
}
printf("frame id:%d\n",framecnt);
av_packet_rescale_ts(&packetout, pCodecCtx_out->time_base, outstream->time_base);
packetout.stream_index = outstream->index;
//将帧写入视频文件中,与av_write_frame的区别是,将对 packet 进行缓存和 pts 检查。
av_interleaved_write_frame(pFormatCtx_out, &packetout);
}
/*************************接收YUV帧数据进行编码并写入文件********end**********************************/
}
}
av_packet_unref(packet);
}
else if(event.type==SDL_KEYDOWN)
{
if(event.key.keysym.sym==SDLK_SPACE)
thread_pause=!thread_pause;
}
else if(event.type==SDL_QUIT)
{
thread_exit=1;
}
else if(event.type==SFM_BREAK_EVENT)
{
break;
}
}
#if OUTPUT_YUV420P
fclose(fp_yuv);
#endif
av_write_trailer(pFormatCtx_out);//程序停止后需要一段时间才能写完mp4文件
// fclose(fp_out);
SDL_Quit();
av_frame_unref(pFrame);
av_frame_unref(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return a.exec();
}