SDL2+FFmpeg5.0播放视频文件

发布时间 2023-11-16 11:54:50作者: 飘杨......

一、概述

  上一节使用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-----------------------
}