ffmpeg+sdl+Qt简易播放器保存mp4文件-边播边存

前言

开发库的版本ffmpeg3.2、sdl2.0、Qt5.12.7,在window下利用mingwin64编译。前面介绍了简易播放器的开发,这次是在前面的基础上,实现实时将解码的YUV数据编码成mp4文件存储。《最简单的FFmpeg+SDL+Qt视频播放器-播放MP4文件_ALANRUOMENG的博客-CSDN博客

流程

ffmpeg+sdl+Qt简易播放器保存mp4文件-边播边存

代码

#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();
}

上一篇:Photoshop将油菜花婚片打造出梦幻的蓝色效果


下一篇:PS教程:把MM照片制作为蓝色梦幻特效