您现在的位置是:首页 > 正文

ffmpeg 音频解码一

2024-04-01 01:13:18阅读 1

1. ffmpeg 视频解码一
2. ffmpeg 视频解码二
3. ffmpeg 音频解码一
4. ffmpeg 音频解码二
5. ffmpeg 音视频解码
6. ffmpeg 视频编码一
7. ffmpeg 视频编码一(精简版)
8. ffmpeg 视频编码二(基于 libswscale 转换视频)
9. ffmpeg 过滤器libavfilter的使用
10. ffmpeg 视频编码三(基于 libavfilter 转换视频)

前言

前面已经介绍了视频的解码流程,这篇开始就开始音频解码了,同样是两篇,一篇使用parser解析器做解析,一篇按常规流程处理。

一些基础知识

  1. 采样率(sample_rate):

    即取样频率,定义了每秒从连续信号中提取并组成离散信号的采样个数,它用赫兹(Hz)来表示。采样频率的倒数是采样周期或者叫作采样时间,它是采样之间的时间间隔。通俗的讲采样频率是指计算机每秒钟采集多少个信号样本。

  2. 采样数(frame_size):

    一帧音频的大小。

  3. 采样格式(sample_fmt):

    音频sample的存储格式。

    可以使用8位无符号整数、16位有符号整数、32位有符号整数以及单精度浮点数,双精度浮点数表示一个采样。但是,没有使用24位的有符号整数,这是因为这些不同的格式使用的是原生的C类型,而C中是没有24位的长度的类型的。

    我们可以使用以下命令查看ffmpeg支持的格式:

    ffplay -sample_fmts
    

    当然也可查看源码,这里贴出 SampleFmtInfo(包含AVSampleFormat相关转化的信息)结构体的源代码:

     static const SampleFmtInfo sample_fmt_info[AV_SAMPLE_FMT_NB] = {
     [AV_SAMPLE_FMT_U8]   = { .name =   "u8", .bits =  8, .planar = 0, .altform = AV_SAMPLE_FMT_U8P  },
     [AV_SAMPLE_FMT_S16]  = { .name =  "s16", .bits = 16, .planar = 0, .altform = AV_SAMPLE_FMT_S16P },
     [AV_SAMPLE_FMT_S32]  = { .name =  "s32", .bits = 32, .planar = 0, .altform = AV_SAMPLE_FMT_S32P },
     [AV_SAMPLE_FMT_S64]  = { .name =  "s64", .bits = 64, .planar = 0, .altform = AV_SAMPLE_FMT_S64P },
     [AV_SAMPLE_FMT_FLT]  = { .name =  "flt", .bits = 32, .planar = 0, .altform = AV_SAMPLE_FMT_FLTP },
     [AV_SAMPLE_FMT_DBL]  = { .name =  "dbl", .bits = 64, .planar = 0, .altform = AV_SAMPLE_FMT_DBLP },
     [AV_SAMPLE_FMT_U8P]  = { .name =  "u8p", .bits =  8, .planar = 1, .altform = AV_SAMPLE_FMT_U8   },
     [AV_SAMPLE_FMT_S16P] = { .name = "s16p", .bits = 16, .planar = 1, .altform = AV_SAMPLE_FMT_S16  },
     [AV_SAMPLE_FMT_S32P] = { .name = "s32p", .bits = 32, .planar = 1, .altform = AV_SAMPLE_FMT_S32  },
     [AV_SAMPLE_FMT_S64P] = { .name = "s64p", .bits = 64, .planar = 1, .altform = AV_SAMPLE_FMT_S64  },
     [AV_SAMPLE_FMT_FLTP] = { .name = "fltp", .bits = 32, .planar = 1, .altform = AV_SAMPLE_FMT_FLT  },
     [AV_SAMPLE_FMT_DBLP] = { .name = "dblp", .bits = 64, .planar = 1, .altform = AV_SAMPLE_FMT_DBL  },
    };
    

    其中name为格式名称,bits是在计算机中所占的位数,plannar是文件存储方式,altform是获取文件根据存储方式不同时相应的名称(例:u8 是 plannar=0 的格式 ,转换为 plannar=1 时 即是 u8p)。

    sample有两种类型的存储方式:平面(planar)和打包(packed),在planar中每一个通道独自占用一个存储平面;在packed中,所有通道的sample交织存储在同一个平面。

  4. 声道信息:

    channels 为 音频的 通道数 1 2 3 4 5…
    channel_layout 为音频 通道格式类型 如 单通道 双通道 …

    对于单声道声音文件,采样数据为八位的短整数(short int 00H-FFH);

    而对于双声道立体声声音文件,每次采样数据为一个16位的整数(int),高八位(左声道)和低八位(右声道)分别代表两个声道。

    如果是双声道(stereo),采样就是双份的,文件也差不多要大一倍。

音频信息

如果音频,样本:fltp;采样率:44100;声道:2。
av_get_bytes_per_sample(fltp) == 4;

  1. AAC(nb_samples和frame_size = 1024)
    则可以得到一帧音频的大小为:
    4 * 2 * 1024 = 8192(字节)
    一帧的播放时间是
    1024*1000000/44100= 46.43ms

  2. MP3(nb_samples和frame_size = 1152)
    则可以得到一帧音频的大小为:
    4 * 2 * 1152= 9216(字节)
    一帧的播放时间是
    1152*1000000/44100= 52.24ms

流程图

在这里插入图片描述

代码流程即如流程图所示,下面讲解一下当中部分函数的作用。

  1. av_parser_init
    这是一个解析器,我们根据解码器,实例化这个解析器,后面解析数据时使用。
  2. av_parser_parse2
    我们从输入文件得到的原始数据(不适用ffmpeg自带的api的话),直接使用是不行的,此时我们就需要把这个原始数据使用上面实例化的解析器来解析,把数据分割成帧,为后面解码数据做准备。
  3. avcodec_send_packet
    发送我们刚刚得到的解析数据到解码器做解码。
  4. avcodec_receive_frame
    获取解码之后的数据。

源码


#pragma once
#define __STDC_CONSTANT_MACROS
#define _CRT_SECURE_NO_WARNINGS

extern "C"
{
#include "libavcodec/avcodec.h"
}

//缓冲区大小(缓存5帧数据)
#define AUDIO_INBUF_SIZE 40960  
/*
	name   depth
	u8        8
	s16      16
	s32      32
	flt      32
	dbl      64
	u8p       8
	s16p     16
	s32p     32
	fltp     32
	dblp     64
	s64      64
	s64p     64
	//此代码解码的音频文件格式如下:
	//AAC文件(一帧1024字节),双声道(2),FLTP(32位,4字节)
	//AAC文件 frame_size 和 nb_samples 大小均为1024
	//一帧音频所占字节大小
	//1024*2*4=8192字节
*/
#define AUDIO_REFILL_THRESH 8192

using namespace std;

#define INPUT_FILE_NAME "lh_online.aac"
#define OUTPUT_FILE_NAME "lh_online.pcm"


static int get_format_from_sample_fmt(const char** fmt,
	enum AVSampleFormat sample_fmt)
{
	int i;
	struct sample_fmt_entry {
		enum AVSampleFormat sample_fmt; const char* fmt_be, * fmt_le;
	} sample_fmt_entries[] = {
		{ AV_SAMPLE_FMT_U8,  "u8",    "u8"    },
		{ AV_SAMPLE_FMT_S16, "s16be", "s16le" },
		{ AV_SAMPLE_FMT_S32, "s32be", "s32le" },
		{ AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
		{ AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
	};
	*fmt = NULL;

	for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
		struct sample_fmt_entry* entry = &sample_fmt_entries[i];
		if (sample_fmt == entry->sample_fmt) {
			*fmt = AV_NE(entry->fmt_be, entry->fmt_le);
			return 0;
		}
	}

	av_log(NULL, AV_LOG_ERROR, "sample format %s is not supported as output format\n", av_get_sample_fmt_name(sample_fmt));
	return -1;
}

static void decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt,
	FILE* ofile)
{
	int i, ch;
	int ret, data_size;
	ret = avcodec_send_packet(dec_ctx, pkt);
	if (ret < 0) {
		av_log(NULL, AV_LOG_ERROR, "发送数据包到解码器出错。\n");
		exit(1);
	}

	while (ret >= 0) {
		ret = avcodec_receive_frame(dec_ctx, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0) {
			av_log(NULL, AV_LOG_ERROR, "Error sending a packet for decoding.\n");
			exit(1);
		}

		printf("frame_number: %d \n", dec_ctx->frame_number);
		//获取每个采样点当中每个声道的大小
		data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
		if (data_size < 0) {
			av_log(NULL, AV_LOG_ERROR, "Failed to calculate data size.\n");
			exit(1);
		}
		//遍历采样点
		for (i = 0; i < frame->nb_samples; i++) {
			//遍历声道
			for (ch = 0; ch < dec_ctx->channels; ch++) {
				fwrite(frame->data[ch] + data_size * i, 1, data_size, ofile);
			}
		}
	}
}

int main(int argc, char* argv[])
{
	const AVCodec* codec;
	AVCodecParserContext* parser;
	AVCodecContext* c = NULL;
	FILE* ifile, * ofile;
	AVFrame* frame;
	AVPacket* pkt;
	uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
	uint8_t* data;
	size_t   data_size;
	int ret,len;
	enum AVSampleFormat sfmt;
	const char* fmt;

	//初始化inbuf数字默认值
	memset(inbuf + AUDIO_INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);

	//获取解码器(此处需要读取的文件是AAC,故)
	codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
	if (!codec) {
		av_log(NULL, AV_LOG_ERROR, "Codec not found.\n");
		exit(1);
	}

	//注册解析器
	parser = av_parser_init(codec->id);
	if (!parser) {
		av_log(NULL, AV_LOG_ERROR, "parser not found.\n");
		exit(1);
	}

	//分配解析器上下文
	c = avcodec_alloc_context3(codec);
	if (!c) {
		av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.\n");
		exit(1);
	}
	
	//打开解码器
	if (avcodec_open2(c, codec, NULL) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Could not open codec.\n");
		exit(1);
	}


	//分配AVPacket
	pkt = av_packet_alloc();
	if (!pkt) {
		exit(1);
	}

	//分配AVFrame
	frame = av_frame_alloc();
	if (!frame) {
		exit(1);
	}

	//打开输入文件
	ifile = fopen(INPUT_FILE_NAME, "rb");
	if (!ifile) {
		av_log(NULL, AV_LOG_ERROR, "Could not open \s.\n", INPUT_FILE_NAME);
		exit(1);
	}
	//打开输入文件
	ofile = fopen(OUTPUT_FILE_NAME, "wb+");
	if (!ofile) {
		av_log(NULL, AV_LOG_ERROR, "Could not open \s.\n", OUTPUT_FILE_NAME);
		exit(1);
	}

	//从输入流 ifile 读取数据到 inbuf 所指向的数组中
	data = inbuf;
	data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, ifile);

	while (data_size > 0) {
		//使用注册的解析器 parser 把数据分割成帧
		ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
			data, data_size,
			AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
		if (ret < 0) {
			fprintf(stderr, "Error while parsing\n");
			exit(1);
		}
		//根据使用情况重置数据位置
		data += ret;
		data_size -= ret;
		//送往解码
		if (pkt->size)
			decode(c, frame, pkt, ofile);

		//判断缓存区剩余数据是否小于一帧音频大小
		//小于的话从文件继续读取,之后在送往解码
		if (data_size < AUDIO_REFILL_THRESH) {
			memmove(inbuf, data, data_size);
			data = inbuf;
			len = fread(data + data_size, 1,
				AUDIO_INBUF_SIZE - data_size, ifile);
			if (len > 0)
				data_size += len;
		}
	}

	//flush 解码器
	decode(c, frame, NULL, ofile);
	//此时就已经解码完了,我们稍后使用ffplay播放下音频
	//解码出来的pcm数据是没有这些基础数据的,我们需要从元数据获取
	//打印下基本信息
	//声道数
	printf("channels: %d \n", c->channels);  
	//采样率
	printf("sample_rate: %d  \n", c->sample_rate);  
	//一帧音频所占字节代销
	printf("buffer: %d  \n", av_samples_get_buffer_size(NULL, c->channels, c->frame_size, c->sample_fmt, 1));
	//采样格式
	sfmt = c->sample_fmt;
	printf("sample_fmt: %s  \n", av_get_sample_fmt_name(sfmt));
	//如果为planar,转换为packed格式
	if (av_sample_fmt_is_planar(sfmt)) {
		const char* packed = av_get_sample_fmt_name(sfmt);
		sfmt = av_get_packed_sample_fmt(sfmt);
	}
	if (get_format_from_sample_fmt(&fmt, sfmt) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Could not get forma \s.\n", av_get_sample_fmt_name(sfmt));
		exit(1);
	}

	//打印播放命令
	printf("Play the output audio file with the command:\n"
		"ffplay -f %s -ac %d -ar %d %s\n",
		fmt, c->channels, c->sample_rate,OUTPUT_FILE_NAME);

	//资源释放
	fclose(ifile);
	fclose(ofile);

	av_parser_close(parser);
	avcodec_free_context(&c);
	av_frame_free(&frame);
	av_packet_free(&pkt);

	return 0;
}

此实例演示了一个将aac文件解码成pcm文件的流程。
打印信息如下:

在这里插入图片描述
可见待解码文件是一个 有2个声道,采样率为44100HZ,采样格式为fltp的文件,共有1478帧。

接下来使用命令播放我们解码出来的音频试试:

ffplay -f f32le -ac 2 -ar 44100 lh_online.pcm

结果:
在这里插入图片描述
此时你应该能听到播放的音频声音,大功告成。

到此,基于parser解析器解码音频的方式就讲述完了。
下一篇和视频一样将讲述纯基于API的方式,应该是比这个方便很多。

网站文章

  • JNI获取并修改Java中类的变量和静态变量的值

    对于JNI的基本使用请移步:Hello JNI本文主要介绍以下几个函数的使用:GetObjectClassGetFieldID,GetStaticFieldIDGet&lt; Type&gt;Fiel...

    2024-04-01 01:12:52
  • 基于单片机的智能宠物监控设计

    基于单片机的智能宠物监控设计

    在本节中宠物管理系统的硬件电路主要包含:供电电路,微处理器最小系统,存储电路,光敏传感器电路,TTL-UART转USB电路,SWD调试及外扩电路接口,按键电路等。此工程项目的开发过程中,熟悉了工程开发...

    2024-04-01 01:12:44
  • ArcGIS批量导出图片

    ArcGIS批量导出图片

    我们在涉及到大量图斑数据或者底图要素需要出图时,就需要借助ArcGIS中的批量出图功能,这样能够减少很多的人力时间。下面是批量导出图斑数据的步骤。所需数据:a.要素数据(矢量的点线面要素数据)b.影像...

    2024-04-01 01:12:36
  • 拼多多的前端面经

    1.说一下vue router的原理 我说了history和hash的原理onhashchange我都说了 原理层说的 他说不对 我整个前后端路由都解释了 他说不对 他问我怎么实现的不刷新 我不知道。2.js、css加载阻塞问题 如果css加载时间长会阻塞后面的渲染吗3.两种图片引用方式? background-image 和 img的区别? 哪个先加载?4.响应式原理...

    2024-04-01 01:12:05
  • 使用MySQL Shell搭建MGR环境

    使用MySQL Shell搭建MGR环境

    更多文章,欢迎关注作者公众号,欢迎一起交流。1)部署 SandBox 环境[root@serverc local]# mysqlshMySQL Shell 8.0.28-commercialCopyr...

    2024-04-01 01:12:01
  • 2022.07.30 Linux矩阵按键驱动笔记1

    这是之前帮客户开发矩阵键盘驱动时记录的笔记。 1. 行GPIO必须为输入,列GPIO必须为输出。 2. 硬件原理图上的引脚与DTS文件中的引脚必须匹配。 3. X30.dts中要配置行,列对应的键值,...

    2024-04-01 01:11:52
  • delphi 判断结构体不为空_数据结构(3)——堆栈的顺序存储实现

    delphi 判断结构体不为空_数据结构(3)——堆栈的顺序存储实现

    堆栈(Stack):顺序存储运算数,需要时倒序输出具有一定操作约束的线性表,只在栈顶做插入删除。插入:入栈删除:出栈后入先出:Last In First Out(LIFO)栈的顺序存储结构通常由一个一...

    2024-04-01 01:11:21
  • docker buildx 构建多平台镜像错误x509: certificate signed by unknown authority

    buildx 构建时,如果去公司内部的仓库拉取镜像时,会遇到下面的这种授权不通过问题。添加到BuildKitd容器中。

    2024-04-01 01:11:15
  • 从"青面兽杨志"护送生辰纲看IT项目管理(不得不转)

    《水浒传》是大家耳熟能详的古典著作之一,我纵然是维吾尔人,但也很早开始接受汉语教育,因此对于该著作也是略知一二。今天这篇文章中,重点讲述下《水浒传》中较为精彩的故事:杨志护送生辰纲前往东京,半路却被晁盖等人劫取。通过这个故事的,可以发现IT团队管理过程中可能出现的若干问题。本篇纯属虚构,若有雷同,那就雷同。开始之前:“青面兽杨志”的出生杨志在《水浒传》中出场是第12回,与刚上梁山不

    2024-04-01 01:10:51
  • 服务器配置vue项目的默认路由,Vue-Router项目设置Nginx伪静态

    Vue-Router项目设置Nginx伪静态使用Vue + Vue-Router的项目,在history模式下,出现了刷新页面404的问题, 服务器为Nginx出现刷新404的原因是Vue-Route...

    2024-04-01 01:10:42