ffmpeg+sdl+Qt简易播放器保存mp4文件-边播边存
2021/11/2 23:10:21
本文主要是介绍ffmpeg+sdl+Qt简易播放器保存mp4文件-边播边存,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言
开发库的版本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(); }
这篇关于ffmpeg+sdl+Qt简易播放器保存mp4文件-边播边存的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南