随笔 - 632  文章 - 17  评论 - 54  阅读 - 93万

SDL2+FFmpeg5.0播放视频文件

一、概述

  上一节使用SDL2播放了YUV视频文件,本节使用SDL2+FFmpeg5.0播放一个视频文件(只播放视频,不播放声音)

  播放效果图:

 

二、代码示例

复制代码
#include "sdl_ffmpeg_play.h"


//sdl刷新事件
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)
//sdl退出事件
#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)

int sdl_thread_exit = 0;

int sfp_refresh_thread(void* opaque) {
    sdl_thread_exit = 0;
    while (!sdl_thread_exit) {
        SDL_Event event;
        event.type = SFM_REFRESH_EVENT;
        SDL_PushEvent(&event);
        SDL_Delay(10);
    }
    sdl_thread_exit = 0;
    //Break
    SDL_Event event;
    event.type = SFM_BREAK_EVENT;
    SDL_PushEvent(&event);

    return 0;
}


SDLFFmpegPlay::SDLFFmpegPlay() {

    AVFormatContext* pFormatCtx;
    int                i, videoindex;
    AVCodecContext* pCodecCtx;
    const AVCodec* pCodec;
    AVFrame* pFrame, * pFrameYUV;
    uint8_t* out_buffer;
    AVPacket* packet;
    int ret, got_picture;

    //------------SDL----------------
    int screen_w, screen_h;
    SDL_Window* screen;
    SDL_Renderer* sdlRenderer;
    SDL_Texture* sdlTexture;
    SDL_Rect sdlRect;
    SDL_Thread* video_tid;
    SDL_Event event;
    char retError[128] = {0};
    struct SwsContext* img_convert_ctx;

    char filepath[] = "E:/tony/demo/visualstudio_workspace/SDLDemo/SDLDemo/videos/diao_si_nan_shi.mov";

    avformat_network_init();
    pFormatCtx = avformat_alloc_context();

    //尝试打开媒体流文件
    ret = avformat_open_input(&pFormatCtx, filepath, NULL, NULL);
    if (ret != 0) {
        av_strerror(ret, retError,sizeof(retError));
        cout << "无法打开文件" << ret << ":" << retError << endl;
        return;
    }

    //获取文件媒体流信息
    ret = avformat_find_stream_info(pFormatCtx, NULL);
    if ( ret < 0) {
        av_strerror(ret, retError, sizeof(retError));
        cout << "无法获取文件媒体流信息:" << ret << ":" << retError << endl;
        avformat_close_input(&pFormatCtx);
        return;
    }

    //查找视频流索引
    videoindex = -1;
    videoindex = av_find_best_stream(pFormatCtx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
    if (AVERROR_STREAM_NOT_FOUND == videoindex) {
        cout << "无法找到视频流" << endl;
        avformat_close_input(&pFormatCtx);
        return;
    }

    //查找解码器
    pCodec = avcodec_find_decoder(pFormatCtx->streams[videoindex]->codecpar->codec_id);
    if (NULL == pCodec) {
        cout << "无法找到解码器" << endl;
        avformat_close_input(&pFormatCtx);
        return;
    }

    //初始化解码器上下文
    pCodecCtx = avcodec_alloc_context3(pCodec);

    //初始化解码器上下文
    ret = avcodec_parameters_to_context(pCodecCtx,pFormatCtx->streams[videoindex]->codecpar);
    if (ret < 0) {
        av_strerror(ret, retError, sizeof(retError));
        cout << "初始化解码器上下文失败:" << ret << ":" << retError << endl;
        avformat_close_input(&pFormatCtx);
        return;
    }

    //打开解码器
    ret = avcodec_open2(pCodecCtx,pCodec,NULL);
    if (ret != 0) {
        av_strerror(ret, retError, sizeof(retError));
        cout << "无法打开解码器:" << ret << ":" << retError << endl;
        avformat_close_input(&pFormatCtx);
        return;
    }

    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();
    pFrameYUV->width = pCodecCtx->width;
    pFrameYUV->height = pCodecCtx->height;
    pFrameYUV->format = AV_PIX_FMT_YUV420P;
    ret = av_frame_get_buffer(pFrameYUV, 0);
    cout << "ret:" << ret << endl;
    cout << "width:" << pCodecCtx->width << endl;
    cout << "height" <<pCodecCtx->height<< endl;
    //给frameYUV分配内存
    //av_frame_get_buffer(pFrameYUV, AV_PIX_FMT_YUV420P);
    //out_buffer = (uint8_t*)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P), pCodecCtx->width, pCodecCtx->height));
    //avpicture_fill((AVPicture*)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

    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 (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) {
        printf("Could not initialize SDL - %s\n", SDL_GetError());
        return;
    }
    //SDL 2.0 Support for multiple windows
    screen_w = pCodecCtx->width;
    screen_h = pCodecCtx->height;
    screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        screen_w, screen_h, SDL_WINDOW_OPENGL);

    if (!screen) {
        printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
        return;
    }
    sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
    //IYUV: Y + U + V  (3 planes)
    //YV12: Y + V + U  (3 planes)
    sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);

    sdlRect.x = 0;
    sdlRect.y = 0;
    sdlRect.w = screen_w;
    sdlRect.h = screen_h;

    packet = (AVPacket*)av_malloc(sizeof(AVPacket));

    video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
    //------------SDL End------------
    //Event Loop
    cout << "开始循环解码" << endl;
    for (;;) {
        //Wait
        SDL_WaitEvent(&event);
        if (event.type == SFM_REFRESH_EVENT) {
            cout << "开始解码" << endl;
            //------------------------------
            if (av_read_frame(pFormatCtx, packet) >= 0) {
                if (packet->stream_index == videoindex) {
                    //把编码数据送入解码器解码
                    ret = avcodec_send_packet(pCodecCtx,packet);//返回0代表解码成功
                    if (ret < 0) {
                        cout << "解码packet失败" << endl;
                        return;
                    }
                    //从解码器中拿出解码后的数据
                    //got_picture = avcodec_receive_frame(pCodecCtx, pFrame);
                    //拿到数据后对数据进行转换
                    while (avcodec_receive_frame(pCodecCtx, pFrame)==0) {
                        cout <<"解码数据->width:"<< pFrame->width<<",height="<<pFrame->height << endl;
                        sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
                        //SDL---------------------------
                        SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
                        SDL_RenderClear(sdlRenderer);
                        //SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );  
                        SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
                        SDL_RenderPresent(sdlRenderer);
                        //SDL End-----------------------
                    }
                }
                av_packet_unref(packet);
            }
            else {
                //Exit Thread
                sdl_thread_exit = 1;
            }
        }
        else if (event.type == SDL_QUIT) {
            sdl_thread_exit = 1;
        }
        else if (event.type == SFM_BREAK_EVENT) {
            break;
        }

    }
    if (img_convert_ctx) {
        sws_freeContext(img_convert_ctx);
    }


    SDL_Quit();
    //--------------
    av_frame_free(&pFrameYUV);
    av_frame_free(&pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
}

SDLFFmpegPlay::~SDLFFmpegPlay() {

}
复制代码

  三、遇到的问题

    1.目标frame未分配内存空间

[swscaler @ 000002a75aa0a700] bad dst image pointers

    解决办法:使用av_frame_get_buffer分配一下即可

    pFrameYUV = av_frame_alloc();
    pFrameYUV->width = pCodecCtx->width;
    pFrameYUV->height = pCodecCtx->height;
    pFrameYUV->format = AV_PIX_FMT_YUV420P;
    ret = av_frame_get_buffer(pFrameYUV, 0);

  2.avcodec_receive_frame用法错误,导致解码后的AVFrame中的数据是空的、宽高为0,错误提示如下

[swscaler @ 0000022b16e2a700] bad src image pointers

  错误用法:

复制代码
//从解码器中拿出解码后的数据
got_picture = avcodec_receive_frame(pCodecCtx, pFrame);
//拿到数据后对数据进行转换
got_picture = avcodec_receive_frame(pCodecCtx, pFrame);
if (got_picture) {
                cout <<"解码数据->width:"<< pFrame->width<<",height="<<pFrame->height << endl;
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
                //SDL---------------------------
                SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
                SDL_RenderClear(sdlRenderer);
                //SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );  
                SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
                SDL_RenderPresent(sdlRenderer);
                //SDL End-----------------------
}
复制代码

  正确用法

复制代码
if (avcodec_receive_frame(pCodecCtx, pFrame)==0) {
                cout <<"解码数据->width:"<< pFrame->width<<",height="<<pFrame->height << endl;
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
                //SDL---------------------------
                SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
                SDL_RenderClear(sdlRenderer);
                //SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );  
                SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
                SDL_RenderPresent(sdlRenderer);
                //SDL End-----------------------
}
复制代码

  

  更新代码:

    上面使用SDL2+FFmpeg播放视频文件并展示的小案例中有一个色差问题。因为视频格式是YUV420P的,但是我们用SDL更新纹理的方式(SDL_UpdateTexture)并不是YUV形式,的需要把更新纹理形式换成如下所示才不会有色差问题:

//由于视频的格式是YUV的,所以SDL更新纹理的方式也需要使用SDL_UpdateYUVTexture,如果使用上面的去更新纹理会出现色差情况
SDL_UpdateYUVTexture(sdlTexture, NULL, 
    pFrameYUV->data[0], pFrameYUV->linesize[0], 
    pFrameYUV->data[1], pFrameYUV->linesize[1], 
    pFrameYUV->data[2], pFrameYUV->linesize[2]);

 

posted on   飘杨......  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
历史上的今天:
2021-11-16 C/C++使用FIFO实现非父子进程之间的通讯
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示