主页 > 创业  > 

FFmpeg入门:最简单的音频播放器

FFmpeg入门:最简单的音频播放器
FFmpeg入门:最简单的音频播放器

欢迎大家来到FFmpeg入门的第二章,今天只做一个最简单的FFmpeg音频播放器;同样,话不多说,先上流程图

流程图

以上流程和视频播放器的解码过程基本上是一致的; 不同点在于 SDL的渲染方式。下面我会重点说一下这个部分

SDL音频渲染

音频渲染的方式和视频不太一样的,我们对于音频的播放速度其实是根据采样率定义的(音频的采样率==视频的帧率),在初始化的时候SDL播放器就指定了这个参数,因此不需要向视频播放器那样手动去延迟来保持帧率。

如下是SDL音频播放器的初始化。

SDL_AudioSpec wanted_spec; wanted_spec.freq = out_sample_rate; // 采样率 wanted_spec.format = AUDIO_S16SYS; // 采样格式 16bit wanted_spec.channels = out_channels; // 通道数 wanted_spec.silence = 0; wanted_spec.samples = out_nb_samples; // 单帧处理的采样点 wanted_spec.callback = fill_audio; // 回调函数 wanted_spec.userdata = pCodecCtx; // 回调函数的参数

其原理就是SDL音频播放器会不断从其缓冲区取出数据读取播放,因此我们只需要不断向其缓冲区中写入数据即可。(详见代码)

// 设置读取的音频数据 audio_info.audio_len = out_buffer_size; audio_info.audio_pos = (Uint8 *) out_buffer;

但是有个点注意一下,就是在写入SDL播放器的缓冲区之前,我们要确保之前的数据已经被SDL播放器消化完了,不然会导致音频数据被覆盖,而没有读出来;(详见代码)

// 等待SDL播放完成 while(audio_info.audio_len > 0) SDL_Delay(0.5); 源代码

接下来看看源代码吧 tutorial03.h

// // tutorial03.h // learning // // Created by chenhuaiyi on 2025/2/16. // #ifndef tutorial03_h #define tutorial03_h /** 头文件 */ #include <stdio.h> // ffmpeg #include <libavcodec/avcodec.h> #include <libswresample/swresample.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <libavutil/imgutils.h> #include <libavutil/time.h> // SDL #include <SDL.h> #include <SDL_thread.h> /** 宏定义 */ #define USE_SDL 1 /** 数据类型定义 */ typedef struct { Uint32 audio_len; Uint8 *audio_pos; } AudioInfo; /** 全局变量 */ extern AudioInfo audio_info; #endif /* tutorial03_h */

tutorial03.c

/** // tutorial03.c // learning // // Created by chenhuaiyi on 2025/2/16. */ #include "tutorial03.h" AudioInfo audio_info; /* udata: 传入的参数 * stream: SDL音频缓冲区 * len: SDL音频缓冲区大小 * 回调函数 */ void fill_audio(void *udata, Uint8 *stream, int len){ SDL_memset(stream, 0, len); // 必须重置,不然全是电音!!! if(audio_info.audio_len==0){ // 有音频数据时才调用 return; } len = (len>audio_info.audio_len ? audio_info.audio_len : len); // 最多填充缓冲区大小的数据 SDL_MixAudio(stream, audio_info.audio_pos, len, SDL_MIX_MAXVOLUME); audio_info.audio_pos += len; audio_info.audio_len -= len; } int main(int argc, char* argv[]) { AVFormatContext* pFormatCtx = avformat_alloc_context(); int i, audioStream; AVCodecContext* pCodecCtx = avcodec_alloc_context3(NULL); const AVCodec* pCodec; AVPacket packet; if(argc < 2) { fprintf(stderr, "Usage: test <file>\n"); exit(1); } avformat_network_init(); // 1. 打开视频文件,获取格式上下文 if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0){ printf("Couldn't open input stream.\n"); return -1; } // 2. 对文件探测流信息 if(avformat_find_stream_info(pFormatCtx, NULL) < 0){ printf("Couldn't find stream information.\n"); return -1; } // 打印信息 av_dump_format(pFormatCtx, 0, argv[1], 0); // 3. 找到对应的音频流 audioStream=-1; for(i=0; i < pFormatCtx->nb_streams; i++) { if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){ audioStream=i; break; } } if(audioStream==-1){ printf("Didn't find a audio stream.\n"); return -1; } // 4. 将音频流编码参数写入上下文 AVCodecParameters* pCodecParam = pFormatCtx->streams[audioStream]->codecpar; avcodec_parameters_to_context(pCodecCtx, pCodecParam); avcodec_parameters_free(&pCodecParam); // 5. 查找流的编码器 pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL){ printf("Codec not found.\n"); return -1; } // 6. 打开流的编解码器 if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){ printf("Could not open codec.\n"); return -1; } // 输出用到的信息 AVChannelLayout out_channel_layout = AV_CHANNEL_LAYOUT_STEREO; // 通道 layout int out_nb_samples = pCodecCtx->frame_size; // 编解码器每个帧需要处理或者输出的采样点的大小 AAC:1024 MP3:1152 enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16; // 采样格式 int out_sample_rate = 44100; // 采样率 int out_channels = out_channel_layout.nb_channels; // 通道数 // 获取需要使用的缓冲区大小 -> 通道数,单通道样本数,位深 1024(单帧处理的采样点)*2(双通道)*2(16bit对应2字节) int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1); // 分配缓冲区空间 uint8_t* out_buffer = NULL; av_samples_alloc(&out_buffer, NULL, out_channels, out_nb_samples, out_sample_fmt, 1); AVFrame* pFrame = av_frame_alloc(); // SDL 初始化 #if USE_SDL if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { printf( "Could not initialize SDL - %s\n", SDL_GetError()); return -1; } SDL_AudioSpec wanted_spec; wanted_spec.freq = out_sample_rate; // 采样率 wanted_spec.format = AUDIO_S16SYS; // 采样格式 16bit wanted_spec.channels = out_channels; // 通道数 wanted_spec.silence = 0; wanted_spec.samples = out_nb_samples; // 单帧处理的采样点 wanted_spec.callback = fill_audio; // 回调函数 wanted_spec.userdata = pCodecCtx; // 回调函数的参数 // 打开音频播放器 if (SDL_OpenAudio(&wanted_spec, NULL)<0){ printf("can't open audio.\n"); return -1; } #endif int ret = 0; int index = 0; // 上下文格式转换 SwrContext *swr_ctx = NULL; swr_alloc_set_opts2(&swr_ctx, &out_channel_layout, // 输出layout out_sample_fmt, // 输出格式 out_sample_rate, // 输出采样率 &pCodecCtx->ch_layout, // 输入layout pCodecCtx->sample_fmt, // 输入格式 pCodecCtx->sample_rate, // 输入采样率 0, NULL); swr_init(swr_ctx); // 开始播放 SDL_PauseAudio(0); AVRational time_base = pFormatCtx->streams[audioStream]->time_base; int64_t av_start_time = av_gettime(); // 播放开始时间戳 // 循环1: 从文件中读取packet while(av_read_frame(pFormatCtx, &packet)>=0){ if(packet.stream_index==audioStream){ // 将packet写入编解码器 ret = avcodec_send_packet(pCodecCtx, &packet); if ( ret < 0 ) { printf("send packet error\n"); return -1; } while (!avcodec_receive_frame(pCodecCtx, pFrame)) { // 格式转化 swr_convert(swr_ctx, &out_buffer, out_buffer_size, (const uint8_t **)pFrame->data, pFrame->nb_samples); index++; printf("第%d帧 | pts:%lld | 帧大小(采样点):%d | 实际播放点%.2fs | 预期播放点%.2fs\n", index, packet.pts, packet.size, (double)(av_gettime() - av_start_time)/AV_TIME_BASE, pFrame->pts * av_q2d(time_base)); #if USE_SDL // 设置读取的音频数据 audio_info.audio_len = out_buffer_size; audio_info.audio_pos = (Uint8 *) out_buffer; // 等待SDL播放完成 while(audio_info.audio_len > 0) SDL_Delay(0.5); #endif } av_packet_unref(&packet); } } // 打印参数 printf("格式: %s\n", pFormatCtx->iformat->name); printf("时长: %lld us\n", pFormatCtx->duration); printf("音频持续时长为 %.2f,音频帧总数为 %d\n", (double)(av_gettime()-av_start_time)/AV_TIME_BASE, index); printf("码率: %lld\n", pFormatCtx->bit_rate); printf("编码器: %s (%s)\n", pCodecCtx->codec->long_name, avcodec_get_name(pCodecCtx->codec_id)); printf("通道数: %d\n", pCodecCtx->ch_layout.nb_channels); printf("采样率: %d \n", pCodecCtx->sample_rate); printf("单通道每帧的采样点数目: %d\n", pCodecCtx->frame_size); printf("pts单位(ms*1000): %.2f\n", av_q2d(pFormatCtx->streams[audioStream]->time_base) * AV_TIME_BASE); // 释放空间 swr_free(&swr_ctx); #if USE_SDL SDL_CloseAudio(); SDL_Quit(); #endif av_free(out_buffer); av_free(pFrame); avcodec_free_context(&pCodecCtx); avformat_close_input(&pFormatCtx); return 0; }
标签:

FFmpeg入门:最简单的音频播放器由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“FFmpeg入门:最简单的音频播放器