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;	// 通道 layoutint out_nb_samples = pCodecCtx->frame_size;	// 编解码器每个帧需要处理或者输出的采样点的大小 AAC:1024  MP3:1152enum 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_SDLif(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;					// 采样格式 16bitwanted_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;}#endifint ret = 0;int index = 0;// 上下文格式转换SwrContext *swr_ctx = NULL;swr_alloc_set_opts2(&swr_ctx,&out_channel_layout,			// 输出layoutout_sample_fmt,					// 输出格式out_sample_rate,				// 输出采样率&pCodecCtx->ch_layout,			// 输入layoutpCodecCtx->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: 从文件中读取packetwhile(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_SDLSDL_CloseAudio();SDL_Quit();
#endifav_free(out_buffer);av_free(pFrame);avcodec_free_context(&pCodecCtx);avformat_close_input(&pFormatCtx);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/896633.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Ubuntu下,源码编译安装Python

在Ubuntu下&#xff0c;源码编译安装Python 知识点 知识点1&#xff1a;在 Linux 系统里&#xff0c;/usr 目录通常用于存放一些共享的、只读的程序和数据&#xff0c;是系统安装软件的一个重要位置。而 /usr/src 目录一般是用来存放系统源代码以及一些软件包的源代码的地方 …

《每天读一个JDK源码》之HashMap解读

&#x1f4cc;《每天读一个JDK源码》之HashMap解读 &#x1f517;源码定位&#xff1a;java.util.HashMap&#xff08;建议IDE对照阅读&#xff09; 今天我们来破解Java集合框架中最精妙的艺术品——HashMap&#xff01;它不仅是面试必考题&#xff08;出现率99%&#xff09;&…

【Java项目】基于SpringBoot的Java学习平台

【Java项目】基于SpringBoot的Java学习平台 技术简介&#xff1a;采用Java技术、SpringBoot框架、MySQL数据库等实现。系统基于B/S架构&#xff0c;前端通过浏览器与后端数据库进行信息交互&#xff0c;后端使用SpringBoot框架和MySQL数据库进行数据处理和存储&#xff0c;实现…

使用ChatGPT-Deep Reaserch两步给出文献综述!

文献综述是学术论文写作中不可或缺的一部分&#xff0c;它不仅是对已有研究的梳理和总结&#xff0c;更是为后续研究奠定理论基础的关键步骤。通过文献综述研究者能够全面了解当前研究领域的现状、主要观点和研究方法&#xff0c;从而找到自己研究的切入点和创新点。这一过程需…

java基础知识(理论篇)

一、java介绍 1.1Java语言 Java 是一种广泛使用的、通用的、面向对象的编程语言&#xff0c;Java 的设计目标是“一次编写&#xff0c;到处运行”&#xff0c;这也这意味着 Java 程序可以在任何支持 Java 的平台&#xff08;如 Windows、Linux、macOS 等&#xff09;上运行。 …

金融赋能绍兴纺织 民生银行助力外贸中小微企业“走出去”

在浙江绍兴&#xff0c;纺织业作为一张熠熠生辉的产业名片&#xff0c;承载着深厚的历史底蕴与蓬勃的发展活力。这里依傍长三角经济圈&#xff0c;交通网络纵横交错&#xff0c;将原材料产地与广阔市场紧密相连&#xff1b;产业集群高度成熟&#xff0c;上下游产业链完备&#…

综合实验处理表格

新建excel表格&#xff0c;输入信息&#xff0c;另存为csv文件。 利用notepad打开csv文件&#xff0c;可以观察格式 目标&#xff1a;通过编程处理文件&#xff0c;实现对数据的处理&#xff0c;成绩求和以及评价 对数据逐行处理&#xff0c;读一行&#xff0c;处理一行&#…

我和我的通义灵码

我和我的通义灵码 我和我的通义灵码个人版&企业版个人版登录个人版workspace应用场景terminal 企业版登录企业版#team docs 体验总结 我和我的通义灵码 说到通义灵码&#xff0c;作为程序员的我们是最有发言权的。从全国首个AI代码助手-通义灵码公测到现在&#xff0c;不知…

一文学会Volatile关键字

引言 在 Java 多线程实战中&#xff0c;volatile 是一个重要的关键字&#xff0c;用于修饰变量&#xff0c;经常在JUC源码中出现&#xff0c;本文详细解析一下这个关键字的奥秘 1. 基本概念 volatile 关键字的主要作用是保证变量的可见性以及在一定程度上禁止指令重排序。在…

Java测试框架Mockito快速入门

Mockito结合TestNG快速入门 什么是Mockito Mockito 是一个专门用于 Java 的强大测试框架&#xff0c;主要用来创建和管理模拟对象&#xff0c;辅助开发者进行单元测试&#xff0c;具有以下特点和功能&#xff1a; 创建模拟对象&#xff1a;能通过简洁的语法创建类或接口的模…

week 3 - More on Collections - Lecture 3

一、Motivation 1. Java支持哪种类型的一维数据结构&#xff1f; Java中用于在单一维度中存储数据的数据结构&#xff0c;如arrays or ArrayLists. 2. 如何在Java下创建一维数据结构&#xff1f;&#xff08;1-dimensional data structure&#xff09; 定义和初始化这些一…

Ubuntu 防火墙iptables和 ufw

文章目录 iptables 和 ufw 的区别Ubuntu 上使用 ufw 配置 iptables 和 ufw 的区别 iptables 和 ufw 是 Linux 系统中用于管理防火墙的工具&#xff0c;但它们的设计目标和使用方式有所不同。 iptables&#xff1a;功能强大&#xff0c;适合高级用户和复杂场景&#xff0c;但配…

(动态规划 最长连续递增子序列)leetcode 674

我上个文章提到了最长递增子序列这个题可以去看看 这个题目翻译人话就是找出最长的递增子串&#xff0c;用一层for循环就行&#xff0c;时间复杂度是O(n) 比起上个题&#xff0c;一个范围多条子序列&#xff08;路径&#xff09;这里一个范围只有一条递增路径&#xff0c;所以…

STM32CubeMx DRV8833驱动

一、DRV8833驱动原理 ​ STBY口接单片机的IO口&#xff0c;STBY置0电机全部停止&#xff0c;置1才能工作。STBY置1后通过AIN1、AIN2、BIN1、BIN2 来控制正反转。 AIN1AIN2电机状态00停止1speed反转speed1正转11停止 其中A端&#xff08;AIN1与AIN2&#xff09;只能控制AO1与…

JSON Schema 入门指南:如何定义和验证 JSON 数据结构

文章目录 一、引言二、什么是 JSON Schema&#xff1f;三、JSON Schema 的基本结构3.1 基本关键字3.2 对象属性3.3 数组元素3.4 字符串约束3.5 数值约束 四、示例&#xff1a;定义一个简单的 JSON Schema五、使用 JSON Schema 进行验证六、实战效果6.1 如何使用 七、总结 一、引…

前端Npm面试题及参考答案

目录 npm 是什么?它的主要作用是什么? npm 包管理工具与 Yarn 有何不同? npm 的 package.json 文件有哪些重要字段? 什么是 npm 依赖?如何在项目中安装、更新和移除依赖? npm 的 node_modules 目录是什么?它的作用是什么? 什么是 npm 脚本?如何在 package.json 中…

零样本思维链(Zero-shot CoT)

Large Language Models are Zero-Shot Reasoners (Kojima et al., 2022) 这篇文章研究了大型语言模型 (LLMs) 在推理任务上的能力&#xff0c;并提出了一种名为 Zero-shot-CoT 的新方法&#xff0c;该方法能够有效地引导 LLM 进行多步骤推理&#xff0c;并在各种推理任务上取得…

day01_Java基础

文章目录 day01_Java基础一、今日课程内容二、Java语言概述&#xff08;了解&#xff09;1、Java语言概述2、为什么要学习Java语言3、Java平台版本说明4、Java特点 三、Java环境搭建&#xff08;操作&#xff09;1、JDK和JRE的概述2、JDK的下载和安装3、IDEA的安装4、IDEA的启动…

设计模式 之 生产消费者模型 (C++)

文章目录 设计模式 之 生产消费者模型 &#xff08;C&#xff09;引言生产消费者模型的基本概念为什么需要生产消费者模型应用场景&#xff1a;C 实现生产消费者模型代码示例代码详细解释共享资源和同步机制生产者函数 producer()消费者函数 consumer()主函数 main() 注意事项总…

Spring Boot 项目开发流程全解析

目录 引言 一、开发环境准备 二、创建项目 三、项目结构 四、开发业务逻辑 1.创建实体类&#xff1a; 2.创建数据访问层&#xff08;DAO&#xff09;&#xff1a; 3.创建服务层&#xff08;Service&#xff09;&#xff1a; 4.创建控制器层&#xff08;Controller&…