FFmpeg 解复用全流程解析

当了解了ffmpeg的 解码 操作后,现可以结合ffmpeg的解复用,将封装好的视频通过sdl播放出来。

解复用流程:

bash 复制代码
					打开视频文件 (avformat_open_input)
						↓
					查找流信息 (avformat_find_stream_info)
						↓
					获得流索引 (for)
						↓
					读取数据包 (av_read_frame)

网图:

编码整体流程:

bash 复制代码
					开始(begin)
						↓
					1. 打开视频文件 (avformat_open_input)⭐️
						↓
					2. 查找流信息 (avformat_find_stream_info)⭐️
						↓
					3. 获得流索引 (for)⭐️
						↓
					4. 查找视频解码器 (avcodec_find_decoder)
						↓
					5. 创建解码器上下文 (avcodec_alloc_context3)
						↓
					6. 将流参数复制到解码器上下文 (avcodec_parameters_to_context)
						↓
					7. 打开解码器 (avcodec_open2)
						↓
					8. 初始化SDL
						↓
					9. 读取数据包 (av_read_frame)⭐️
						↓
					10. 发送数据包到解码器 (avcodec_send_packet)
						↓
					11. 接收解码帧 (avcodec_receive_frame)
						↓
					12. SDL更新、渲染
						↓
					13. 冲刷解码器
						↓
					14. 释放所有资源 
						↓
					结束(end)

包含头文件:

cpp 复制代码
#include <iostream>
#include <fstream>
#include <chrono>

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavutil/avutil.h"
#include "libavformat/avformat.h"
}

#include "SDL.h"

1. 解复用函数

1.1 avformat_open_input

avformat_open_input 是 FFmpeg 库中用于打开媒体文件或流的核心函数,它的主要任务是读取媒体文件的头部信息,识别其封装格式,并构建一个包含所有必要信息的上下文结构体,为后续的解码操作做准备。

①函数原型

cpp 复制代码
int avformat_open_input(
    AVFormatContext **ps,
    const char *url,
    const AVInputFormat *fmt,
    AVDictionary **options
);

②参数说明

参数 类型 说明
ps AVFormatContext ** 输出参数,指向创建好的格式上下文。调用前可传 NULL,函数内部自动分配内存
url const char * 媒体源地址,支持本地文件路径、RTSP/RTMP 流地址、HTTP 视频链接等
fmt const AVInputFormat * 强制指定输入格式,传 NULL 时 FFmpeg 自动探测格式
options AVDictionary ** 可选参数字典,用于设置超时、缓存、协议参数等,不需要可传 NULL

③返回值

成功:返回 0 失败:返回一个负值的错误码(AVERROR 枚举)

④使用示例

cpp 复制代码
AVFormatContext *demux_ctx = nullptr;
int re = avformat_open_input(&demux_ctx, "video1.mp4", NULL, NULL);
if (0 != re) {
    std::cout << "avformat_open_input failed!" << std::endl;
    PrintErr(re);
    goto FAILED;
}

1.2 avformat_find_stream_info

avformat_find_stream_info 是 FFmpeg 中用于获取媒体文件完整流信息的关键函数。它通过读取并分析文件的部分数据包,来填充 AVFormatContext 中关于视频流、音频流等更精确的参数。

①函数原型

cpp 复制代码
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

②参数说明

参数 类型 说明
ic AVFormatContext * 已通过 avformat_open_input() 打开的格式上下文
options AVDictionary ** 可选参数字典(可传 NULL),用于控制探测行为

③返回值

成功:返回 0 失败:返回一个负值的错误码(AVERROR 枚举)

④使用示例

cpp 复制代码
re = avformat_find_stream_info(demux_ctx, NULL);
if (0 != re){
    std::cout << "avformat_find_stream_info failed!" << std::endl;
    PrintErr(re);
    goto FAILED;
}

1.3 avcodec_parameters_to_context

avcodec_parameters_to_context 是一个用于将流参数(AVCodecParameters) 拷贝到编解码器上下文(AVCodecContext) 中的函数。

①函数原型

cpp 复制代码
int avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par);

②参数说明

参数 类型 说明
codec AVCodecContext * ‌目标‌解码器上下文,必须已通过 avcodec_alloc_context3() 分配好
par const AVCodecParameters * ‌源‌参数,通常来自 fmt_ctx->streamsi->codecpar

③返回值

成功:返回 0 失败:返回一个负值的错误码(AVERROR 枚举)

④使用示例

cpp 复制代码
re = avcodec_parameters_to_context(codec_ctx, demux_ctx->streams[video_index]->codecpar);
if (re < 0) {
    std::cout << "avcodec_parameters_to_context failed!" << std::endl;
    PrintErr(re);
    goto FAILED;
}

1.4 av_read_frame

av_read_frame 是 FFmpeg 中用于从媒体文件中读取压缩数据包的核心函数。它处于解封装(Demuxing)流程的最后一环,负责将封装格式(如 MP4、FLV)中的音视频数据,以一个个独立的压缩数据包(AVPacket)的形式提取出来,为后续的解码操作做准备。。

①函数原型

cpp 复制代码
int av_read_frame(AVFormatContext *s, AVPacket *pkt);

②参数说明

参数 类型 说明
s AVFormatContext * 已通过 avformat_open_input() 打开的格式上下文
pkt AVPacket * 输出参数,用于存储读取到的压缩数据包。‌不能传 NULL,必须提前分配空间‌

③返回值

成功:返回 0,表示成功读取一个数据包 失败:返回一个负值的错误码(AVERROR 枚举)

④使用示例

cpp 复制代码
while (1) {
    // 读取数据包
    re = av_read_frame(demux_ctx, pkt);
    if (re == AVERROR_EOF) break;      // 正常读完
    if (0 != re) {
        std::cout << "av_read_frame failed!" << std::endl;
        PrintErr(re);
        goto FAILED;
    }
    
    if (video_index == pkt->stream_index) {
        // 视频处理
    } else if (audio_index == pkt->stream_index) {
        // 音频处理
    } else {
        // other index
    }
}

2. 编码流程

2.1 打开视频文件 avformat_open_input ⭐️

cpp 复制代码
AVFormatContext *demux_ctx = nullptr;

int re = avformat_open_input(&demux_ctx, "video1.mp4", NULL, NULL);
if (0 != re) {
    std::cout << "avformat_open_input failed!" << std::endl;
    PrintErr(re);
    goto FAILED;
}

2.2 查找流信息 avformat_find_stream_info ⭐️

cpp 复制代码
re = avformat_find_stream_info(demux_ctx, NULL);
if (0 != re){
    std::cout << "avformat_find_stream_info failed!" << std::endl;
    PrintErr(re);
    goto FAILED;
}

2.3 获得流索引 ⭐️

cpp 复制代码
int video_index = -1, audio_index = -1;
// 遍历所有流
for (unsigned int i = 0; i < demux_ctx->nb_streams; ++i) {
    if (AVMEDIA_TYPE_VIDEO == demux_ctx->streams[i]->codecpar->codec_type) {
        video_index = i;
        std::cout << "video stream index : " << video_index << std::endl;

    } else if (AVMEDIA_TYPE_AUDIO == demux_ctx->streams[i]->codecpar->codec_type) {
        audio_index = i;
        std::cout << "audio stream index : " << audio_index << std::endl;
    } else {
        std::cout << "other stream index : " << i << std::endl;
    }
}

新版本可以使用新API获取:

cpp 复制代码
// 新版本可以使用新API获取
int video_index = av_find_best_stream(demux_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
int audio_index = av_find_best_stream(demux_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);

2.4 查找视频解码器 avcodec_find_decoder

cpp 复制代码
AVCodecID codec_id = AV_CODEC_ID_NONE;
const AVCodec *codec = nullptr;

codec_id = demux_ctx->streams[video_index]->codecpar->codec_id;
codec = avcodec_find_decoder(codec_id);
if (!codec) {
    std::cout << "avcodec_find_decoder failed!" << std::endl;
    goto FAILED;
}

2.5 创建解码器上下文 avcodec_alloc_context3

cpp 复制代码
AVCodecContext *codec_ctx = nullptr;

codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
    std::cout << "avcodec_alloc_context3 failed!" << std::endl;
    goto FAILED;
}

2.6 将流参数复制到解码器上下文 avcodec_parameters_to_context

cpp 复制代码
re = avcodec_parameters_to_context(codec_ctx, demux_ctx->streams[video_index]->codecpar);
if (re < 0) {
    std::cout << "avcodec_parameters_to_context failed!" << std::endl;
    PrintErr(re);
    goto FAILED;
}

2.7 打开解码器 avcodec_open2

cpp 复制代码
re = avcodec_open2(codec_ctx, codec, NULL);
if (0 != re) {
    std::cout << "avcodec_open2 failed!" << std::endl;
    PrintErr(re);
    goto FAILED;
}

2.8 初始化SDL部分

cpp 复制代码
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *sdl_win = nullptr;
SDL_Renderer *renderer = nullptr;
SDL_Texture *texture = nullptr;

// 创建窗口
sdl_win = SDL_CreateWindow("标题", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                            winW, winH, 0);
if (!sdl_win) {
    std::cout << "SDL_CreateWindow failed!" << std::endl;
    goto FAILED;
}

// 创建渲染器
renderer = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
    std::cout << "SDL_CreateRenderer failed!" << std::endl;
    goto FAILED;
}

// 创建 纹理|材质
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
                            winW,
                            winH);
if (!texture) {
    std::cout << "SDL_CreateTexture failed!" << std::endl;
    goto FAILED;
}

2.9 读取数据包 av_read_frame ⭐️

cpp 复制代码
while (1) {
    // 9.读取数据包
    re = av_read_frame(demux_ctx, pkt);
    if (re == AVERROR_EOF) break;      // 正常读完
    if (0 != re) {
        std::cout << "av_read_frame failed!" << std::endl;
        PrintErr(re);
        goto FAILED;
    }

	// other code ...
}

2.10 发送数据包到解码器 avcodec_send_packet

cpp 复制代码
AVPacket *pkt = av_packet_alloc();

re = avcodec_send_packet(codec_ctx, pkt);
av_packet_unref(pkt);
if (0 != re) {
    std::cout << "avcodec_send_packet failed!" << std::endl;
    PrintErr(re);
    goto FAILED;
}

2.11 接收解码帧 avcodec_receive_frame

cpp 复制代码
AVFrame *frame = av_frame_alloc();

while (0 <= re) {
    // 11.接收解码帧
    re = avcodec_receive_frame(codec_ctx, frame);
    if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
        // 还没有完整的数据 || 已经读完了
        break;
    }
    if (0 > re) {
        std::cout << "avcodec_receive_frame failed!" << std::endl;
        PrintErr(re);
        goto FAILED;
    }

	// other code ...
}

2.12 SDL更新、渲染

cpp 复制代码
SDL_UpdateYUVTexture(texture, NULL, frame->data[0], frame->linesize[0],
        frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, &rect);
SDL_RenderPresent(renderer);

2.13 冲刷解码器

cpp 复制代码
re = avcodec_send_packet(codec_ctx, NULL);		// 发送NULL
if (0 != re) {
    std::cout << "avcodec_send_packet failed!" << std::endl;
    PrintErr(re);
    goto FAILED;
}

while (0 <= re) {
    re = avcodec_receive_frame(codec_ctx, frame);
    if (AVERROR(EAGAIN) == re) {
        // 还没有完整的数据
        continue;
    }
    if (AVERROR_EOF == re) {
        // 已经读完了
        break;
    }
    // 读取失败
    if (0 > re) {
        std::cout << "avcodec_receive_frame failed!" << std::endl;
        PrintErr(re);
        goto FAILED;
    }

    std::cout << "- ";


    // SDL更新、渲染
    SDL_UpdateYUVTexture(texture, NULL, frame->data[0], frame->linesize[0],
            frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);
    SDL_RenderClear(renderer);
    SDL_RenderCopy(renderer, texture, NULL, &rect);
    SDL_RenderPresent(renderer);
}

2.14 释放所有资源

cpp 复制代码
avformat_close_input(&demux_ctx);
avcodec_free_context(&codec_ctx);
av_packet_free(&pkt);
av_frame_free(&frame);

if (texture) {
    SDL_DestroyTexture(texture);
}
if (renderer) {
    SDL_DestroyRenderer(renderer);
}
if (sdl_win) {
    SDL_DestroyWindow(sdl_win);
}
SDL_Quit();

2.15 其它

① 假设播放的视频宽高大于屏幕,如果不做调整,显示将会超出屏幕,显示不全;可以根据屏幕和视频宽高计算出实际需要显示的宽高;

cpp 复制代码
int winW = 0, winH = 0;
SDL_Rect rect;		// 用于sdl

// 如果视频宽高大于屏幕宽高,则已屏幕宽高为准
{
    int screen_width = 1920;
    int screen_height = 1080;

    // 获得屏幕宽高
    SDL_DisplayMode mode;
    if (0 == SDL_GetCurrentDisplayMode(0, &mode)) {
        screen_width = mode.w;
        screen_height = mode.h;
    }

    int video_width = demux_ctx->streams[video_index]->codecpar->width;
    int video_height = demux_ctx->streams[video_index]->codecpar->height;
    winW = video_width;
    winH = video_height;

    // 宽高比
    double rate = (double)video_width / video_height;

    // 视频宽 > 屏幕宽
    if (winW > screen_width) {
        winW = screen_width;
        winH = (int)(screen_width / rate + 0.5);
    }

    // 视频高 > 屏幕高
    if (winH > screen_height) {
        winH = screen_height;
        winW = (int)(screen_height * rate + 0.5);
    }

    rect.h = winH;
    rect.w = winW;
    rect.x = 0;
    rect.y = 0;

    std::cout << "video: " << video_width << "x" << video_height << std::endl;
    std::cout << "play: " << winW << "x" << winH << std::endl;
}

② 计算播放时每秒的帧率

cpp 复制代码
int fps = 0;
std::chrono::steady_clock::time_point fpsStart;             // 每秒统计起点

fpsStart = std::chrono::steady_clock::now();
// ...

// -------------- 帧率统计 --------------
++fps;
auto fpsEnd = std::chrono::steady_clock::now();
auto elapsedSinceFps = std::chrono::duration_cast<std::chrono::milliseconds>(fpsEnd - fpsStart).count();
if (1000 <= elapsedSinceFps) {
    std::cout << "fps = " << fps << std::endl;
    fps = 0;
    fpsStart = std::chrono::steady_clock::now();     // 重置统计起点
}

③ 计算每帧需要的延时

cpp 复制代码
std::chrono::steady_clock::time_point frameStart;           // 每帧起始时间点

frameStart = std::chrono::steady_clock::now();
// ...

// -------------- 帧间隔控制(维持目标帧率) --------------  不是很准确,但看不出
auto frameEnd = std::chrono::steady_clock::now();
auto elapsedFrame = std::chrono::duration_cast<std::chrono::milliseconds>(frameEnd - frameStart).count();
int delay = sleep_time - static_cast<int>(elapsedFrame);
if (0 < delay) {
    SDL_Delay(delay);
}

frameStart = std::chrono::steady_clock::now();

3. 代码合集

读取本地.mp4文件,解复用、解码,使用sdl渲染显示;

cpp 复制代码
#include <iostream>
#include <fstream>
#include <chrono>

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavutil/avutil.h"
#include "libavformat/avformat.h"
}

#include "SDL.h"

//using namespace std::chrono;

static void PrintErr(int ret) {
    char buf[1024] = { 0 };
    av_strerror(ret, buf, sizeof(buf));
    std::cout << buf << std::endl;
}

#undef main
int main(void)
{
    std::cout << av_version_info() << std::endl;



    // 解复用
    AVFormatContext *demux_ctx = nullptr;
    int video_index = -1, audio_index = -1;


    // 解码
    AVCodecID codec_id = AV_CODEC_ID_NONE;
    const AVCodec *codec = nullptr;
    AVCodecContext *codec_ctx = nullptr;


    AVPacket *pkt = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();


    // SDL
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window *sdl_win = nullptr;
    SDL_Renderer *renderer = nullptr;
    SDL_Texture *texture = nullptr;
    int winW = 0, winH = 0;
    SDL_Rect rect;


    // 计算帧率
    double speed = 1.0;         // 播放速度倍率
    int rate = 25;              // 视频原始帧率
    int sleep_time = (int)(1000 / (speed * rate) + 0.5);        // 每帧间隔(毫秒)

    int fps = 0;
    std::chrono::steady_clock::time_point fpsStart;             // 每秒统计起点
    std::chrono::steady_clock::time_point frameStart;           // 每帧起始时间点



    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 解复用初始化


    // 1.打开视频文件
    int re = avformat_open_input(&demux_ctx, "video1.mp4", NULL, NULL);
    if (0 != re) {
        std::cout << "avformat_open_input failed!" << std::endl;
        PrintErr(re);
        goto FAILED;
    }

    // 2.查找流信息
    re = avformat_find_stream_info(demux_ctx, NULL);
    if (0 != re){
        std::cout << "avformat_find_stream_info failed!" << std::endl;
        PrintErr(re);
        goto FAILED;
    }


    // 3.获得流索引
    for (unsigned int i = 0; i < demux_ctx->nb_streams; ++i) {      // 遍历所有流
        if (AVMEDIA_TYPE_VIDEO == demux_ctx->streams[i]->codecpar->codec_type) {
            video_index = i;
            std::cout << "video stream index : " << video_index << std::endl;

        } else if (AVMEDIA_TYPE_AUDIO == demux_ctx->streams[i]->codecpar->codec_type) {
            audio_index = i;
            std::cout << "audio stream index : " << audio_index << std::endl;
        } else {
            std::cout << "other stream index : " << i << std::endl;
        }
    }

    if (-1 == video_index) {
        std::cout << "video stream can't find!" << std::endl;
        goto FAILED;
    }

    // 新版本可以使用新API获取
    //int video_index = av_find_best_stream(demux_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    //int audio_index = av_find_best_stream(demux_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////




    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 解码初始化

    // 4.查找视频解码器
    codec_id = demux_ctx->streams[video_index]->codecpar->codec_id;
    codec = avcodec_find_decoder(codec_id);
    if (!codec) {
        std::cout << "avcodec_find_decoder failed!" << std::endl;
        goto FAILED;
    }


    // 5.创建解码器上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        std::cout << "avcodec_alloc_context3 failed!" << std::endl;
        goto FAILED;
    }

    // 6.将流参数复制到解码器上下文
    re = avcodec_parameters_to_context(codec_ctx, demux_ctx->streams[video_index]->codecpar);
    if (re < 0) {
        std::cout << "avcodec_parameters_to_context failed!" << std::endl;
        PrintErr(re);
        goto FAILED;
    }

    // 7.打开解码器
    re = avcodec_open2(codec_ctx, codec, NULL);
    if (0 != re) {
        std::cout << "avcodec_open2 failed!" << std::endl;
        PrintErr(re);
        goto FAILED;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////



    // 如果视频宽高大于屏幕宽高,则已屏幕宽高为准
    {
        int screen_width = 1920;
        int screen_height = 1080;

        // 获得屏幕宽高
        SDL_DisplayMode mode;
        if (0 == SDL_GetCurrentDisplayMode(0, &mode)) {
            screen_width = mode.w;
            screen_height = mode.h;
        }

        int video_width = demux_ctx->streams[video_index]->codecpar->width;
        int video_height = demux_ctx->streams[video_index]->codecpar->height;
        winW = video_width;
        winH = video_height;

        // 宽高比
        double rate = (double)video_width / video_height;

        // 视频宽 > 屏幕宽
        if (winW > screen_width) {
            winW = screen_width;
            winH = (int)(screen_width / rate + 0.5);
        }

        // 视频高 > 屏幕高
        if (winH > screen_height) {
            winH = screen_height;
            winW = (int)(screen_height * rate + 0.5);
        }

        rect.h = winH;
        rect.w = winW;
        rect.x = 0;
        rect.y = 0;

        std::cout << "video: " << video_width << "x" << video_height << std::endl;
        std::cout << "play: " << winW << "x" << winH << std::endl;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// SDL初始化

    // 8.初始化SDL部分

    // 创建窗口
    sdl_win = SDL_CreateWindow("标题", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                                winW, winH, 0);
    if (!sdl_win) {
        std::cout << "SDL_CreateWindow failed!" << std::endl;
        goto FAILED;
    }

    // 创建渲染器
    renderer = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED);
    if (!renderer) {
        std::cout << "SDL_CreateRenderer failed!" << std::endl;
        goto FAILED;
    }

    // 创建 纹理|材质
    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
                                winW,
                                winH);
    if (!texture) {
        std::cout << "SDL_CreateTexture failed!" << std::endl;
        goto FAILED;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////


    {
        // 获得视频实际帧率
        AVRational framerate = demux_ctx->streams[video_index]->avg_frame_rate;                // 平均帧率,通常由容器格式提供,是推荐的帧率来源
        if (0 >= framerate.num || 0 >= framerate.den) {
            framerate = demux_ctx->streams[video_index]->r_frame_rate;              // 真实帧率,有些格式会提供,但可能不如 avg_frame_rate 稳定
        }
        if (0 >= framerate.num || 0 >= framerate.den) {
            // 保底,默认25帧
            framerate = { 25, 1 };
        }
        rate = av_q2d(framerate);                                   // 获得视频实际帧率
        sleep_time = (int)(1000 / (speed * rate) + 0.5);            // 计算每帧需要睡眠的时间
        std::cout << "rate: " << rate << std::endl;
        std::cout << "sleep_time: " << sleep_time << std::endl;
    }


    fpsStart = std::chrono::steady_clock::now();
    frameStart = std::chrono::steady_clock::now();
    while (1) {
        // 9.读取数据包
        re = av_read_frame(demux_ctx, pkt);
        if (re == AVERROR_EOF) break;      // 正常读完
        if (0 != re) {
            std::cout << "av_read_frame failed!" << std::endl;
            PrintErr(re);
            goto FAILED;
        }


        // 判断如果当前读取的帧不是视频,则跳过处理;这里只处理视频
        if (video_index != pkt->stream_index) {
            av_packet_unref(pkt);
            continue;
        }

        // 10.发送数据包到解码器
        re = avcodec_send_packet(codec_ctx, pkt);
        av_packet_unref(pkt);
        if (0 != re) {
            std::cout << "avcodec_send_packet failed!" << std::endl;
            PrintErr(re);
            goto FAILED;
        }

        while (0 <= re) {
            // 11.接收解码帧
            re = avcodec_receive_frame(codec_ctx, frame);
            if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
                // 还没有完整的数据 || 已经读完了
                break;
            }
            if (0 > re) {
                std::cout << "avcodec_receive_frame failed!" << std::endl;
                PrintErr(re);
                goto FAILED;
            }

            std::cout << ". ";

            // 12.SDL更新、渲染
            SDL_UpdateYUVTexture(texture, NULL, frame->data[0], frame->linesize[0],
                    frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);
            SDL_RenderClear(renderer);
            SDL_RenderCopy(renderer, texture, NULL, &rect);
            SDL_RenderPresent(renderer);


            // -------------- 帧率统计 --------------
            ++fps;
            auto fpsEnd = std::chrono::steady_clock::now();
            auto elapsedSinceFps = std::chrono::duration_cast<std::chrono::milliseconds>(fpsEnd - fpsStart).count();
            if (1000 <= elapsedSinceFps) {
                std::cout << "fps = " << fps << std::endl;
                fps = 0;
                fpsStart = std::chrono::steady_clock::now();     // 重置统计起点
            }


            // -------------- 帧间隔控制(维持目标帧率) --------------  不是很准确,但看不出
            auto frameEnd = std::chrono::steady_clock::now();
            auto elapsedFrame = std::chrono::duration_cast<std::chrono::milliseconds>(frameEnd - frameStart).count();
            int delay = sleep_time - static_cast<int>(elapsedFrame);
            if (0 < delay) {
                SDL_Delay(delay);
            }

            frameStart = std::chrono::steady_clock::now();
        }
    }


    // 13.冲刷解码器
    re = avcodec_send_packet(codec_ctx, NULL);
    if (0 != re) {
        std::cout << "avcodec_send_packet failed!" << std::endl;
        PrintErr(re);
        goto FAILED;
    }

    while (0 <= re) {
        re = avcodec_receive_frame(codec_ctx, frame);
        if (AVERROR(EAGAIN) == re) {
            // 还没有完整的数据
            continue;
        }
        if (AVERROR_EOF == re) {
            // 已经读完了
            break;
        }
        // 读取失败
        if (0 > re) {
            std::cout << "avcodec_receive_frame failed!" << std::endl;
            PrintErr(re);
            goto FAILED;
        }

        std::cout << "- ";


        // SDL更新、渲染
        SDL_UpdateYUVTexture(texture, NULL, frame->data[0], frame->linesize[0],
                frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);
        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, texture, NULL, &rect);
        SDL_RenderPresent(renderer);
    }


FAILED:
    // 14.释放所有资源
    avformat_close_input(&demux_ctx);
    avcodec_free_context(&codec_ctx);
    av_packet_free(&pkt);
    av_frame_free(&frame);

    if (texture) {
        SDL_DestroyTexture(texture);
    }
    if (renderer) {
        SDL_DestroyRenderer(renderer);
    }
    if (sdl_win) {
        SDL_DestroyWindow(sdl_win);
    }
    SDL_Quit();

    return 0;
}

4. 视频效果截图

到此,解复用、解码全流程结束!

相关推荐
程序员老陆8 小时前
FFmpeg时间基相关函数大杂烩
ffmpeg·时间基·timebase
程序员老陆1 天前
FFmpeg libswresample 模块的关键函数swr_convert 到底在干嘛?
ffmpeg·音视频·格式转换·音频重采样
海兰1 天前
ffmpeg-wasm 完整安装指南(Ubuntu 24.04)
ubuntu·ffmpeg·wasm
≮傷£≯√2 天前
QT配置FFmpeg
开发语言·qt·ffmpeg
Everbrilliant892 天前
Android FFmpeg 实战:从基础到播放器的完整技术解析
android·ffmpeg·ffmepg实战·音视频同步实现·ffmpegpractices·ffmpegpractice·opengl 视频渲染
苏灿烤鱼3 天前
从微信公众号内容到视频号视频:自动化视频生成的技术实现
人工智能·python·ffmpeg
桐桐桐3 天前
视频画面裁剪与尺寸压缩实战:ffmpeg crop/scale 用法与在线替代
ffmpeg·音视频·视频
程序员老陆3 天前
音频为什么不编码成“声卡格式”(例如S16)?因为声卡只负责响,编码器只负责省
ffmpeg·音视频
程序员老陆4 天前
FFmpeg -tune参数全景解析:zerolatency、film、animation 到底在调什么?
ffmpeg·音视频·编码