FFmpeg8+SDL3实现音视频播放

环境:Windows10,VS2022,ffmpeg-n8.0-latest-win64-lgpl-shared-8.0,SDL3-3.2.22

// FFmpeg + SDL3 简易音视频播放(同步性较弱,适合作为入门示例)
#include <iostream>
#include <thread>
#include <chrono>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>  // 添加这个头文件
}

#include <SDL3/SDL.h>
#include <SDL3/SDL_audio.h>

int main(int argc, char* argv[]) {
    const char* filename = "D:/Videos/32674156398-1-192.mp4";

    // ---------- FFmpeg 初始化 ----------
    av_log_set_level(AV_LOG_INFO);

    AVFormatContext* fmt_ctx = nullptr;
    if (avformat_open_input(&fmt_ctx, filename, nullptr, nullptr) < 0) {
        std::cerr << "无法打开输入文件: " << filename << std::endl;
        return -1;
    }
    if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
        std::cerr << "无法找到流信息" << std::endl;
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    int video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    int audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
    if (video_stream_index < 0) { std::cerr << "未找到视频流\n"; avformat_close_input(&fmt_ctx); return -1; }
    if (audio_stream_index < 0) { std::cerr << "未找到音频流\n"; avformat_close_input(&fmt_ctx); return -1; }

    // 视频解码上下文
    AVStream* vstream = fmt_ctx->streams[video_stream_index];
    const AVCodec* vcodec = avcodec_find_decoder(vstream->codecpar->codec_id);
    if (!vcodec) { std::cerr << "找不到视频解码器\n"; return -1; }
    AVCodecContext* vctx = avcodec_alloc_context3(vcodec);
    avcodec_parameters_to_context(vctx, vstream->codecpar);
    if (avcodec_open2(vctx, vcodec, nullptr) < 0) { std::cerr << "无法打开视频解码器\n"; return -1; }

    // 音频解码上下文(注意使用 codecpar 获取原始参数)
    AVStream* astream = fmt_ctx->streams[audio_stream_index];
    AVCodecParameters* a_par = astream->codecpar;
    const AVCodec* acodec = avcodec_find_decoder(a_par->codec_id);
    if (!acodec) { std::cerr << "找不到音频解码器\n"; return -1; }
    AVCodecContext* actx = avcodec_alloc_context3(acodec);
    avcodec_parameters_to_context(actx, a_par);
    if (avcodec_open2(actx, acodec, nullptr) < 0) { std::cerr << "无法打开音频解码器\n"; return -1; }

    std::cout << "视频分辨率: " << vctx->width << "x" << vctx->height << std::endl;
    std::cout << "音频: channels=" << a_par->ch_layout.nb_channels << " sample_rate=" << a_par->sample_rate << std::endl;

    // ---------- SDL3 初始化 ----------
    if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS)) {
        std::cerr << "无法初始化 SDL3: " << SDL_GetError() << std::endl;
        return -1;
    }

    // 创建窗口、渲染器、纹理(用于显示 YUV420P)
    SDL_Window* window = SDL_CreateWindow("FFmpeg + SDL3 player",
        vctx->width, vctx->height, SDL_WINDOW_RESIZABLE);
    if (!window) { std::cerr << "SDL_CreateWindow 错误: " << SDL_GetError() << std::endl; return -1; }

    SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
    if (!renderer) { std::cerr << "SDL_CreateRenderer 错误: " << SDL_GetError() << std::endl; return -1; }

    SDL_Texture* texture = SDL_CreateTexture(renderer,
        SDL_PIXELFORMAT_IYUV,
        SDL_TEXTUREACCESS_STREAMING,
        vctx->width, vctx->height);
    if (!texture) { std::cerr << "SDL_CreateTexture 错误: " << SDL_GetError() << std::endl; return -1; }

    // ---------- 视频转换上下文(pix fmt -> YUV420P) ----------
    SwsContext* sws_ctx = sws_getContext(vctx->width, vctx->height, vctx->pix_fmt,
        vctx->width, vctx->height, AV_PIX_FMT_YUV420P,
        SWS_BILINEAR, nullptr, nullptr, nullptr);
    AVFrame* vframe = av_frame_alloc();
    AVFrame* yuv_frame = av_frame_alloc();
    int yuv_buf_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, vctx->width, vctx->height, 1);
    uint8_t* yuv_buf = (uint8_t*)av_malloc(yuv_buf_size);
    av_image_fill_arrays(yuv_frame->data, yuv_frame->linesize, yuv_buf,
        AV_PIX_FMT_YUV420P, vctx->width, vctx->height, 1);

    // ---------- 音频重采样设置:将输入转换为 S16 (native) ,保持原始采样率 & 声道数 ----------
    SwrContext* swr_ctx = swr_alloc();

    // 设置输入参数 - 使用新的channel_layout API
    av_opt_set_chlayout(swr_ctx, "in_chlayout", &a_par->ch_layout, 0);
    av_opt_set_int(swr_ctx, "in_sample_rate", a_par->sample_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", (AVSampleFormat)a_par->format, 0);

    // 设置输出参数
    av_opt_set_chlayout(swr_ctx, "out_chlayout", &a_par->ch_layout, 0);
    av_opt_set_int(swr_ctx, "out_sample_rate", a_par->sample_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);

    if (swr_init(swr_ctx) < 0) {
        std::cerr << "swr_init 失败\n";
        return -1;
    }

    // ---------- 使用 SDL_OpenAudioDeviceStream 创建音频流(SDL3 风格) ----------
    SDL_AudioSpec desired_spec;
    SDL_zero(desired_spec);
    desired_spec.freq = a_par->sample_rate;
    desired_spec.format = SDL_AUDIO_S16; // 对应 AV_SAMPLE_FMT_S16
    desired_spec.channels = a_par->ch_layout.nb_channels;

    // 打开一个 AudioStream(方便迁移自 SDL2):
    SDL_AudioStream* sdl_audio_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired_spec, nullptr, nullptr);
    if (!sdl_audio_stream) {
        std::cerr << "SDL_OpenAudioDeviceStream 失败: " << SDL_GetError() << std::endl;
        return -1;
    }
    // 设备开始是 paused,需要显式 resume 才会播放(见文档)
    if (!SDL_ResumeAudioStreamDevice(sdl_audio_stream)) {
        std::cerr << "SDL_ResumeAudioStreamDevice 失败: " << SDL_GetError() << std::endl;
        // 但不强制退出,继续尝试后续播放
    }

    // ---------- AVPacket / 读帧循环准备 ----------
    AVPacket* pkt = av_packet_alloc();
    AVFrame* afr = av_frame_alloc();

    // 简单帧率延时(注意:真实播放器应基于 pts 做同步)
    double fps = av_q2d(vstream->r_frame_rate);
    if (fps <= 0.0) fps = 25.0;
    int64_t delay_ms = static_cast<int64_t>(1000.0 / fps);

    bool quit = false;
    SDL_Event event;

    while (!quit) {
        // 处理窗口事件(保证可以关闭窗口)
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_EVENT_QUIT) {
                quit = true;
            }
        }

        if (av_read_frame(fmt_ctx, pkt) < 0) {
            // 文件读完
            break;
        }

        if (pkt->stream_index == video_stream_index) {
            // 视频解码
            if (avcodec_send_packet(vctx, pkt) == 0) {
                while (avcodec_receive_frame(vctx, vframe) == 0) {
                    sws_scale(sws_ctx, vframe->data, vframe->linesize, 0, vctx->height, yuv_frame->data, yuv_frame->linesize);

                    SDL_UpdateYUVTexture(texture, nullptr,
                        yuv_frame->data[0], yuv_frame->linesize[0],
                        yuv_frame->data[1], yuv_frame->linesize[1],
                        yuv_frame->data[2], yuv_frame->linesize[2]);

                    SDL_RenderClear(renderer);
                    SDL_RenderTexture(renderer, texture, nullptr, nullptr);
                    SDL_RenderPresent(renderer);

                    std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
                }
            }
        }
        else if (pkt->stream_index == audio_stream_index) {
            // 音频解码
            if (avcodec_send_packet(actx, pkt) == 0) {
                while (avcodec_receive_frame(actx, afr) == 0) {
                    // 估算重采样后需要的输出样本数
                    int64_t delay = swr_get_delay(swr_ctx, a_par->sample_rate);
                    int out_nb_samples = (int)av_rescale_rnd(delay + afr->nb_samples, a_par->sample_rate, a_par->sample_rate, AV_ROUND_UP);

                    // 分配临时缓冲 - 修正参数顺序
                    uint8_t** out_planes = nullptr;
                    int out_linesize = 0;
                    if (av_samples_alloc_array_and_samples(&out_planes, &out_linesize,
                        a_par->ch_layout.nb_channels, out_nb_samples, AV_SAMPLE_FMT_S16, 0) < 0) {
                        std::cerr << "av_samples_alloc_array_and_samples 失败\n";
                        continue;
                    }

                    // 进行重采样(转换到 S16)
                    int converted = swr_convert(swr_ctx, out_planes, out_nb_samples,
                        (const uint8_t**)afr->data, afr->nb_samples);
                    if (converted < 0) {
                        std::cerr << "swr_convert 失败\n";
                        av_freep(&out_planes[0]);
                        av_freep(&out_planes);
                        continue;
                    }

                    int out_buffer_size = av_samples_get_buffer_size(&out_linesize, a_par->ch_layout.nb_channels, converted, AV_SAMPLE_FMT_S16, 1);
                    if (out_buffer_size < 0) out_buffer_size = 0;

                    // 将 PCM 数据送入 SDL 音频流(SDL3)
                    if (out_buffer_size > 0) {
                        if (!SDL_PutAudioStreamData(sdl_audio_stream, out_planes[0], out_buffer_size)) {
                            std::cerr << "SDL_PutAudioStreamData 失败: " << SDL_GetError() << std::endl;
                        }
                    }

                    // 释放临时缓冲
                    av_freep(&out_planes[0]);
                    av_freep(&out_planes);
                }
            }
        }

        av_packet_unref(pkt);
    }

    // 等待音频队列播放完(简单等待,不做严格同步)
    SDL_Delay(500);

    // ---------- 释放资源 ----------
    av_frame_free(&vframe);
    av_frame_free(&afr);
    av_packet_free(&pkt);

    sws_freeContext(sws_ctx);
    av_free(yuv_buf);
    av_frame_free(&yuv_frame);

    swr_free(&swr_ctx);

    if (sdl_audio_stream) {
        SDL_DestroyAudioStream(sdl_audio_stream); // 会同时关闭设备
    }

    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    avcodec_free_context(&vctx);
    avcodec_free_context(&actx);
    avformat_close_input(&fmt_ctx);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值