天天看点

FFmpeg 利用 SDL2 播放视频

使用 SDL2 渲染屏幕的主要流程:

示例:

extern "C"
{
#include "libavcodec\avcodec.h"
#include "libavformat\avformat.h"
#include "libswscale\swscale.h"
#include "libavutil\imgutils.h"
}

#include "SDL.h"
#include "SDL_thread.h"

#include <iostream>
#include <fstream>
#include <memory>
#include <Windows.h>

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    // 注册所有的格式和解码器
    av_register_all();

    // 初始化 SDL 库
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
    {
        std::cerr << "Could not initialize SDL - " << SDL_GetError() << std::endl;
        return -;
    }

    AVFormatContext *pFmtCtx = NULL;
    // 打开视频文件,读取文件头信息到 AVFormatContext 结构体中
    if (avformat_open_input(&pFmtCtx, lpCmdLine, NULL, NULL) != )
    {
        return -;
    }
    // 程序结束时关闭 AVFormatContext
    std::shared_ptr<AVFormatContext*> fmtCtxCloser(&pFmtCtx, avformat_close_input);

    // 读取流信息到 AVFormatContext->streams 中
    // AVFormatContext->streams 是一个数组,数组大小是 AVFormatContext->nb_streams
    if (avformat_find_stream_info(pFmtCtx, NULL) < )
    {
        return -;
    }

    // 找到第一个视频流
    int videoStream = -;
    for (decltype(pFmtCtx->nb_streams) i = ; i < pFmtCtx->nb_streams; ++i)
    {
        if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -)
    {
        return -;
    }

    // 获取解码器上下文
    AVCodecParameters *pCodecParams = pFmtCtx->streams[videoStream]->codecpar;

    // 获取解码器
    AVCodec *pCodec = avcodec_find_decoder(pCodecParams->codec_id);
    if (pCodec == NULL)
    {
        std::cerr << "Unsupported codec!" << std::endl;
        return -;
    }

    // 解码器上下文
    AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);  // allocate
    if (avcodec_parameters_to_context(pCodecCtx, pCodecParams) < ) // initialize
    {
        return -;
    }
    // 程序结束时关闭解码器
    std::shared_ptr<AVCodecContext> codecCtxCloser(pCodecCtx, avcodec_close);

    // 打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < )
    {
        return -;
    }

    // 创建帧
    AVFrame *pFrame = av_frame_alloc();
    std::shared_ptr<AVFrame*> frameDeleter(&pFrame, av_frame_free);

    // 创建转换后的帧
    AVFrame *pFrameYUV = av_frame_alloc();
    std::shared_ptr<AVFrame*> frameBGRDeleter(&pFrameYUV, av_frame_free);

    const AVPixelFormat destPixFormat = AV_PIX_FMT_BGR24;  // AV_PIX_FMT_YUV420P

    // 开辟数据存储区
    int numBytes = av_image_get_buffer_size(destPixFormat, pCodecCtx->width, pCodecCtx->height, );
    uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
    // 程序结束时释放
    std::shared_ptr<uint8_t> bufferDeleter(buffer, av_free);

    // 填充 pFrameBGR 中的若干字段(data、linsize等)
    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, buffer, destPixFormat,
        pCodecCtx->width, pCodecCtx->height, );

    // 获取图像处理上下文
    SwsContext *pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
        pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, destPixFormat,
        SWS_BICUBIC, NULL, NULL, NULL);
    // 程序结束时释放
    std::shared_ptr<SwsContext> swsCtxDeleter(pSwsCtx, sws_freeContext);

    // 创建 SDL screen
    SDL_Window *screen = SDL_CreateWindow("Demo", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        pCodecCtx->width, pCodecCtx->height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
    if (screen == NULL)
    {
        std::cerr << "Could not create window - " << SDL_GetError() << std::endl;
        return -;
    }
    std::shared_ptr<SDL_Window> screenDestoryer(screen, SDL_DestroyWindow);

    // 创建 Renderer
    SDL_Renderer *renderer = SDL_CreateRenderer(screen, -, SDL_RENDERER_TARGETTEXTURE);
    std::shared_ptr<SDL_Renderer> rendererDestroyer(renderer, SDL_DestroyRenderer);

    // 创建 texture
    Uint32 sdlPixFormat = SDL_PIXELFORMAT_BGR24;  // SDL_PIXELFORMAT_YV12
    SDL_Texture *texture = SDL_CreateTexture(renderer, sdlPixFormat, 
        SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
    std::shared_ptr<SDL_Texture> textureDestroyer(texture, SDL_DestroyTexture);

    SDL_Event event;

    AVPacket packet;
    int frameCount = ;
    const int saveFrameIndex = ;
    while (av_read_frame(pFmtCtx, &packet) >= )  // 将 frame 读取到 packet
    {
        // 迭代结束后释放 av_read_frame 分配的 packet 内存
        std::shared_ptr<AVPacket> packetDeleter(&packet, av_packet_unref);

        if (packet.stream_index == videoStream)  // 如果读到的是视频流
        {
            // 使用解码器 pCodecCtx 将 packet 解码
            avcodec_send_packet(pCodecCtx, &packet);
            // 返回 pCodecCtx 解码后的数据,注意只有在解码完整个 frame 时该函数才返回 0 
            if (avcodec_receive_frame(pCodecCtx, pFrame) == )
            {
                // 图像转换
                sws_scale(pSwsCtx, pFrame->data,
                    pFrame->linesize, , pCodecCtx->height,
                    pFrameYUV->data, pFrameYUV->linesize);

                // 渲染图片
                SDL_Rect rc = { , , pCodecCtx->width, pCodecCtx->height };
                SDL_UpdateTexture(texture, &rc, pFrameYUV->data[], pFrameYUV->linesize[]);
                SDL_RenderClear(renderer);
                SDL_RenderCopy(renderer, texture, &rc, &rc);
                SDL_RenderPresent(renderer);

                SDL_Delay();  // 延迟100毫秒
            }
        }

        // 处理消息
        SDL_PollEvent(&event);
        switch (event.type)
        {
        case SDL_QUIT:
            SDL_Quit();
            return ;
        }
    }

    SDL_Quit();
    return ;
}
           

继续阅读