环境: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;
}

3万+

被折叠的 条评论
为什么被折叠?



