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

news2025/3/5 23:37:56

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;
}

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

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

相关文章

基于Linux系统的物联网智能终端

背景 产品研发和项目研发有什么区别&#xff1f;一个令人发指的问题&#xff0c;刚开始工作时项目开发居多&#xff0c;认为项目开发和产品开发区别不大&#xff0c;待后来随着自身能力的提升&#xff0c;逐步感到要开发一个好产品还是比较难的&#xff0c;我认为项目开发的目的…

Netty笔记9:粘包半包

Netty笔记1&#xff1a;线程模型 Netty笔记2&#xff1a;零拷贝 Netty笔记3&#xff1a;NIO编程 Netty笔记4&#xff1a;Epoll Netty笔记5&#xff1a;Netty开发实例 Netty笔记6&#xff1a;Netty组件 Netty笔记7&#xff1a;ChannelPromise通知处理 Netty笔记8&#xf…

LabVIEW虚拟弗兰克赫兹实验仪

随着信息技术的飞速发展&#xff0c;虚拟仿真技术已经成为教学和研究中不可或缺的工具。开发了一种基于LabVIEW平台开发的虚拟弗兰克赫兹实验仪&#xff0c;该系统不仅能模拟实验操作&#xff0c;还能实时绘制数据图形&#xff0c;极大地丰富了物理实验的教学内容和方式。 ​ …

spring boot + vue 搭建环境

参考文档&#xff1a;https://blog.csdn.net/weixin_44215249/article/details/117376417?fromshareblogdetail&sharetypeblogdetail&sharerId117376417&sharereferPC&sharesourceqxpapt&sharefromfrom_link. spring boot vue 搭建环境 一、浏览器二、jd…

清华团队提出HistoCell,从组织学图像推断超分辨率细胞空间分布助力癌症研究|顶刊精析·25-03-02

小罗碎碎念 今天和大家分享一篇2025-02-21发表于nature communications的文章&#xff0c;内容涉及病理空转单细胞。 从组织学图像推断细胞空间分布对癌症研究意义重大&#xff0c;但现有方法存在标注工作量大、分辨率或特征挖掘不足等局限。研究旨在开发一种高效准确的方法。 …

html+js 轮播图

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>轮播图示例</title><style>/* 基本样式…

vue3:初学 vue-router 路由配置

承上一篇&#xff1a;nodejs&#xff1a;express js-mdict 作为后端&#xff0c;vue 3 vite 作为前端&#xff0c;在线查询英汉词典 安装 cnpm install vue-router -S 现在讲一讲 vue3&#xff1a;vue-router 路由配置 cd \js\mydict-web\src mkdir router cd router 我还…

Python 爬取唐诗宋词三百首

你可以使用 requests 和 BeautifulSoup 来爬取《唐诗三百首》和《宋词三百首》的数据。以下是一个基本的 Python 爬虫示例&#xff0c;它从 中华诗词网 或类似的网站获取数据并保存为 JSON 文件。 import requests from bs4 import BeautifulSoup import json import time# 爬取…

C语言408考研先行课第一课:数据类型

由于408要考数据结构……会有算法题…… 所以&#xff0c;需要C语言来进行一个预备…… 因为大一贪玩&#xff0c;C语言根本没学进去……谁能想到考研还用得到呢&#xff1f;【手动doge&#xff08;bushi&#xff09; 软件用的是Clion&#xff0c;可以自行搜索教程下载使用。…

03 HarmonyOS Next仪表盘案例详解(二):进阶篇

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 文章目录 前言1. 响应式设计1.1 屏幕适配1.2 弹性布局 2. 数据展示与交互2.1 数据卡片渲染2.2 图表区域 3. 事件处理机制3.1 点击事件处理3.2 手势…

探秘基带算法:从原理到5G时代的通信变革【四】Polar 编解码(一)

文章目录 2.3 Polar 编解码2.3.1 Polar 码简介与发展背景2.3.2 信道极化理论基础对称容量与巴氏参数对称容量 I ( W ) I(W) I(W)巴氏参数 Z ( W ) Z(W) Z(W)常见信道信道联合信道分裂信道极化 本博客为系列博客&#xff0c;主要讲解各基带算法的原理与应用&#xff0c;包括&…

基础篇(一)强化学习是什么?从零开始理解智能体的学习过程

强化学习是什么&#xff1f;从零开始理解智能体的学习过程 你是否曾好奇过&#xff0c;人工智能是如何在复杂的环境中学会做出决策的&#xff1f;无论是打游戏的AI&#xff0c;还是自动驾驶的汽车&#xff0c;还是最近很火的DeepSeek它们的背后都离不开一种强大的技术——强化…

如何直接导出某个conda环境中的包, 然后直接用 pip install -r requirements.txt 在新环境中安装

1. 导出 Conda 环境配置 conda list --export > conda_requirements.txt这将生成一个 conda_requirements.txt 文件&#xff0c;其中包含当前环境中所有包的列表及其版本信息。 2. 转换为 requirements.txt 文件 grep -v "^#" conda_requirements.txt | cut -d …

基于 HTML、CSS 和 JavaScript 的智能九宫格图片分割系统

目录 1 前言 2 技术实现 2.1 HTML 结构 2.2 CSS 样式 2.3 JavaScript 交互 3 代码解析 3.1 HTML 部分 3.2 CSS 部分 3.3 JavaScript 部分 4 完整代码 5 运行结果 6 总结 6.1 系统特点 6.2 使用方法 1 前言 在当今数字化的时代&#xff0c;图片处理需求日益增长。…

委托者模式(掌握设计模式的核心之一)

目录 问题&#xff1a; 举例&#xff1a; 总结&#xff1a;核心就是利用Java中的多态来完成注入。 问题&#xff1a; 今天刷面经&#xff0c;刷到装饰者模式&#xff0c;又进阶的发现委托者模式&#xff0c;发现还是不理解&#xff0c;特此记录。 举例&#xff1a; ​老板​…

MySQL-高级查询

查询处理 排序&#xff08;默认不是按主键排序的&#xff09; order by 字段1[&#xff0c;字段2] [asc|desc] 默认是升序排序也可以指定 select 列表中列的序号进行排序如果是多个字段&#xff0c;那么在上一个字段排序完的基础上排序下一个 限制数量 limit 行数&#xff0…

Apache Kafka单节点极速部署指南:10分钟搭建开发单节点环境

Apache Kafka单节点极速部署指南&#xff1a;10分钟搭建开发单节点环境 Kafka简介&#xff1a; Apache Kafka是由LinkedIn开发并捐赠给Apache基金会的分布式流处理平台&#xff0c;现已成为实时数据管道和流应用领域的行业标准。它基于高吞吐、低延迟的设计理念&#xff0c;能够…

Redis7——进阶篇(一)

前言&#xff1a;此篇文章系本人学习过程中记录下来的笔记&#xff0c;里面难免会有不少欠缺的地方&#xff0c;诚心期待大家多多给予指教。 基础篇&#xff1a; Redis&#xff08;一&#xff09;Redis&#xff08;二&#xff09;Redis&#xff08;三&#xff09;Redis&#x…

Sourcetrail 代码分析工具

Sourcetrail 概述 Sourcetrail 是一个代码分析工具&#xff0c;它旨在帮助开发人员理解和导航复杂的代码库。它可以创建代码库的可视化图形&#xff0c;显示代码中的类、函数、变量、依赖关系等信息&#xff0c;从而帮助开发人员更好地理解代码结构和关系&#xff0c;降低维护…

从数据到决策,永洪科技助力良信电器“智”领未来

在数字经济浪潮汹涌的时代&#xff0c;数字化转型已成为企业增强竞争力、实现可持续发展的必由之路。良信电器&#xff0c;作为国内知名的电气设备制造企业&#xff0c;积极响应时代号召&#xff0c;携手永洪科技&#xff0c;共同开启了数字化转型的新篇章。 上海良信电器股份有…