2023-03-17:使用Go语言和FFmpeg库实现音频重采样解码,并将其保存为PCM格式的文件。
创始人
2025-05-29 18:17:12
0

2023-03-17:使用Go语言和FFmpeg库实现音频重采样解码,并将其保存为PCM格式的文件。

答案2023-03-17:

在音视频处理领域,常常需要对音频进行重采样和解码,以便于后续的处理和分析。本文将介绍如何使用Go语言及FFmpeg库实现音频重采样解码为PCM数据的过程。

1.前置知识和背景介绍

在介绍音频重采样解码之前,我们需要了解几个基本概念:

音频采样率:指音频信号每秒钟采样的次数,通常用赫兹(Hz)表示。常见的采样率有44100Hz、48000Hz等。

音频编码格式:指把声音转成数字信号后所采用的编码格式,常见的编码格式有MP3、AAC、WAV等。

音频重采样:指改变音频采样率的过程,也可以理解为对音频做插值运算,使得原来采样率与目标采样率不一致的音频能够适配到目标采样率上。

音频解码:指把已经编码压缩的音频文件解码成原始的音频数据流的过程。

2.实现步骤

实现音频重采样解码为PCM数据的具体步骤如下:

2.1.导入所需的FFmpeg库和Go语言包

首先,我们需要导入一些必要的FFmpeg库和Go语言包,以便后续代码中能够正常调用相关接口和方法。代码示例如下:

import ("fmt""os""unsafe""github.com/moonfdd/ffmpeg-go/ffcommon""github.com/moonfdd/ffmpeg-go/libavcodec""github.com/moonfdd/ffmpeg-go/libavformat""github.com/moonfdd/ffmpeg-go/libavutil""github.com/moonfdd/ffmpeg-go/libswresample"
)

2.2.打开输入音频文件

需要打开输入音频文件,并检查是否打开成功。若无法打开,则应该返回错误信息。

fmtCtx := libavformat.AvformatAllocContext()
if libavformat.AvformatOpenInput(&fmtCtx, inFileName, nil, nil) < 0 {fmt.Printf("Cannot open input file.\n")return
}

其中,inFileName是输入音频文件名。

2.3.获取音频流信息

获取音频流信息,包括音频流的相关参数(采样率、声道数、采样格式等),并检查是否获取成功。若无法获取成功,则应该返回错误信息。

if fmtCtx.AvformatFindStreamInfo(nil) < 0 {fmt.Printf("Cannot find stream info in input file.\n")return
}aStreamIndex := -1
for i := uint32(0); i < fmtCtx.NbStreams; i++ {if fmtCtx.GetStream(i).Codecpar.CodecType == libavutil.AVMEDIA_TYPE_AUDIO {aStreamIndex = int(i)break}
}if aStreamIndex == -1 {fmt.Printf("Cannot find audio stream.\n")return
}aCodecPara := fmtCtx.GetStream(uint32(aStreamIndex)).Codecpar

其中,aCodecPara是音频流的参数。

2.4.查找音频解码器并打开音频解码器

根据音频流的参数,查找对应的音频解码器,并打开解码器。在打开解码器时,需要将音频流的参数设置为解码器的参数。

codec := libavcodec.AvcodecFindDecoder(aCodecPara.CodecId)
if codec == nil {fmt.Printf("Cannot find any codec for audio.\n")return
}codecCtx = codec.AvcodecAllocContext3()if codecCtx.AvcodecParametersToContext(aCodecPara) < 0 {fmt.Printf("Cannot alloc codec context.\n")return
}if codecCtx.AvcodecOpen2(codec, nil) < 0 {fmt.Printf("Cannot open audio codec.\n")return
}

其中,codecCtx是解码器的上下文。

2.5.计算重采样参数

计算重采样后的采样率、声道数和采样格式等参数。

out_channel_layout := codecCtx.ChannelLayout
out_sample_fmt := libavutil.AV_SAMPLE_FMT_S16
out_sample_rate := int32(44100)
out_channels := libavutil.AvGetChannelLayoutNbChannels(out_channel_layout)

2.6.初始化重采样上下文

根据输入和输出参数,初始化重采样上下文

swrCtx := libswresample.SwrAllocSetOpts(nil,out_channel_layout, out_sample_fmt, out_sample_rate,codecCtx.ChannelLayout, codecCtx.SampleFmt(), int(codecCtx.SampleRate()), 0, nil,
)if swrCtx == nil {fmt.Printf("Cannot allocate resampler context.\n")return
}if swrCtx.SwrInit() < 0 {fmt.Printf("Cannot initialize resampling context.\n")return
}

其中,swrCtx是重采样上下文。

2.7.分配AVPacket和AVFrame

分别分配AVPacket和AVFrame,用于从输入音频流中读取数据、向解码器传递数据和从解码器接收数据等操作。

pkt := libavcodec.AvPacketAlloc()
defer pkt.AvPacketFree()frame := libavutil.AvFrameAlloc()
defer frame.AvFrameFree()

2.8.从输入音频流中读取数据,并将其送入解码器进行解码

循环从输入音频流中读取数据,并将数据送入解码器进行解码。若读取到的数据为空,则跳出循环。

for {ret := fmtCtx.AvReadFrame(pkt)if ret < 0 {break}if pkt.StreamIndex() != int32(aStreamIndex) {continue}ret = codecCtx.AvCodecSendPacket(pkt)if ret < 0 {fmt.Printf("Error sending a packet for decoding.\n")break}for {ret = codecCtx.AvCodecReceiveFrame(frame)if ret < 0 {break}// 进行重采样data := make([]*uint8, 0)for i := 0; i < int(frame.NbSamples()); i++ {data = append(data, (*uint8)(frame.ExtendedData(0)[i]))}out_nb_samples := swrCtx.SwrGetDelay(int64(codecCtx.SampleRate())) + int(frame.NbSamples())out_samples_per_channel := out_nb_samples * out_channelsout_buffer := libavutil.AvMalloc(uintptr(out_samples_per_channel) * uintptr(libavutil.AvGetBytesPerSample(out_sample_fmt)))defer libavutil.AvFree(out_buffer)out_data := (**uint8)(unsafe.Pointer(&out_buffer))swrCtx.SwrConvert(out_data, out_nb_samples, data, int(frame.NbSamples()))}pkt.AvPacketUnref()
}

注意,在解码时需要根据每个AVPacket的stream index判断是否是目标音频流。

2.9.编写PCM数据到文件中

将重采样后的PCM数据写入输出文件中。

outFile, err := os.Create(outFileName)
if err != nil {fmt.Printf("Can not create output file.\n")return
}
defer outFile.Close()samples_size := libavutil.AvGetBytesPerSample(out_sample_fmt)
for i := 0; i < out_samples_per_channel; i++ {for j := 0; j < out_channels; j++ {sample_value := *(*int16)(unsafe.Pointer(uintptr(out_buffer) + uintptr(i*out_channels+j)*uintptr(samples_size)))binary.Write(outFile, binary.LittleEndian, &sample_value)}
}

其中,outFileName是输出音频文件名。

3.go语言完整代码

// https://feater.top/ffmpeg/ffmpeg-audio-resample-decode-mp3-to-pcm-with-cpu
package mainimport ("fmt""os""os/exec""unsafe""github.com/moonfdd/ffmpeg-go/ffcommon""github.com/moonfdd/ffmpeg-go/libavcodec""github.com/moonfdd/ffmpeg-go/libavformat""github.com/moonfdd/ffmpeg-go/libavutil""github.com/moonfdd/ffmpeg-go/libswresample"
)const MAX_AUDIO_FRAME_SIZE = 192000func main() {os.Setenv("Path", os.Getenv("Path")+";./lib")ffcommon.SetAvutilPath("./lib/avutil-56.dll")ffcommon.SetAvcodecPath("./lib/avcodec-58.dll")ffcommon.SetAvdevicePath("./lib/avdevice-58.dll")ffcommon.SetAvfilterPath("./lib/avfilter-56.dll")ffcommon.SetAvformatPath("./lib/avformat-58.dll")ffcommon.SetAvpostprocPath("./lib/postproc-55.dll")ffcommon.SetAvswresamplePath("./lib/swresample-3.dll")ffcommon.SetAvswscalePath("./lib/swscale-5.dll")genDir := "./out"_, err := os.Stat(genDir)if err != nil {if os.IsNotExist(err) {os.Mkdir(genDir, 0777) //  Everyone can read write and execute}}inVFileName := "./out/test.mp3"outFileName := "./out/test16.pcm"// ./lib/ffmpeg -i ./resources/big_buck_bunny.mp4 -acodec libmp3lame -vn ./out/test.mp3//是否存在mp3文件_, err = os.Stat(inVFileName)if err != nil {if os.IsNotExist(err) {fmt.Println("create mp3 file")exec.Command("./lib/ffmpeg", "-i", "./resources/big_buck_bunny.mp4", "-acodec", "libmp3lame", "-vn", inVFileName, "-y").CombinedOutput()}}os.Remove(outFileName)f, err := os.OpenFile(outFileName, os.O_CREATE|os.O_RDWR, 0777)if err != nil {fmt.Println("open file failed,err:", err)return}fmtCtx := libavformat.AvformatAllocContext()var codecCtx *libavcodec.AVCodecContextpkt := libavcodec.AvPacketAlloc()frame := libavutil.AvFrameAlloc()aStreamIndex := -1for {if libavformat.AvformatOpenInput(&fmtCtx, inVFileName, nil, nil) < 0 {fmt.Printf("Cannot open input file.\n")break}if fmtCtx.AvformatFindStreamInfo(nil) < 0 {fmt.Printf("Cannot find stream info in input file.\n")break}fmtCtx.AvDumpFormat(0, inVFileName, 0)//查找视频流在文件中的位置for i := uint32(0); i < fmtCtx.NbStreams; i++ {if fmtCtx.GetStream(i).Codecpar.CodecType == libavutil.AVMEDIA_TYPE_AUDIO {aStreamIndex = int(i)break}}if aStreamIndex == -1 {fmt.Printf("Cannot find audio stream.\n")return}aCodecPara := fmtCtx.GetStream(uint32(aStreamIndex)).Codecparcodec := libavcodec.AvcodecFindDecoder(aCodecPara.CodecId)if codec == nil {fmt.Printf("Cannot find any codec for audio.\n")return}codecCtx = codec.AvcodecAllocContext3()if codecCtx.AvcodecParametersToContext(aCodecPara) < 0 {fmt.Printf("Cannot alloc codec context.\n")return}codecCtx.PktTimebase = fmtCtx.GetStream(uint32(aStreamIndex)).TimeBaseif codecCtx.AvcodecOpen2(codec, nil) < 0 {fmt.Printf("Cannot open audio codec.\n")return}//设置转码参数out_channel_layout := codecCtx.ChannelLayoutout_sample_fmt := libavutil.AV_SAMPLE_FMT_S16out_sample_rate := int32(44100) //codecCtx.SampleRateout_channels := libavutil.AvGetChannelLayoutNbChannels(out_channel_layout)audio_out_buffer := libavutil.AvMalloc(MAX_AUDIO_FRAME_SIZE * 2)var swr_ctx *libswresample.SwrContextswr_ctx = swr_ctx.SwrAllocSetOpts(int64(out_channel_layout),libavutil.AVSampleFormat(out_sample_fmt),out_sample_rate,int64(codecCtx.ChannelLayout),codecCtx.SampleFmt,codecCtx.SampleRate,0, uintptr(0))swr_ctx.SwrInit()for (fmtCtx.AvReadFrame(pkt)) >= 0 {if pkt.StreamIndex == uint32(aStreamIndex) {if codecCtx.AvcodecSendPacket(pkt) >= 0 {for codecCtx.AvcodecReceiveFrame(frame) >= 0 {/*Planar(平面),其数据格式排列方式为 (特别记住,该处是以点nb_samples采样点来交错,不是以字节交错):LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)而不带P的数据格式(即交错排列)排列方式为:LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)*/if libavutil.AvSampleFmtIsPlanar(codecCtx.SampleFmt) != 0 {len0 := swr_ctx.SwrConvert((**byte)(unsafe.Pointer(&audio_out_buffer)),MAX_AUDIO_FRAME_SIZE*2,(**byte)(unsafe.Pointer(&frame.Data)),frame.NbSamples)if len0 < 0 {continue}dst_bufsize := libavutil.AvSamplesGetBufferSize(nil,out_channels,len0,libavutil.AVSampleFormat(out_sample_fmt),1)bytes := []byte{}ptr := audio_out_bufferfor i := int32(0); i < dst_bufsize; i++ {bytes = append(bytes, *(*byte)(unsafe.Pointer(ptr)))ptr++}f.Write(bytes)}}}}pkt.AvPacketUnref()}break}libavutil.AvFrameFree(&frame)libavcodec.AvPacketFree(&pkt)codecCtx.AvcodecClose()libavcodec.AvcodecFreeContext(&codecCtx)fmtCtx.AvformatFreeContext()f.Close()fmt.Println("-----------------------------------------")// ./lib/ffplay -ar 44100 -ac 2 -f s16le -i ./out/test.pcm_, err = exec.Command("./lib/ffplay.exe", "-ar", "44100", "-ac", "2", "-f", "s16le", "-i", "./out/test16.pcm").Output()if err != nil {fmt.Println("play err = ", err)}
}

4.测试和演示结果

go run ./examples/a16.audio_decode_swr_mp32pcm/main.go

在这里插入图片描述

5.结论

通过调用Go语言和FFmpeg库提供的接口和方法,我们可以轻松实现音频重采样解码,并将其保存为PCM格式的文件。这对于音视频应用开发和研究等领域非常有帮助。在实际工作中,我们可以根据具体需求和场景,进一步优化和扩展相关功能。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
JAVA多线程知识整理 Java多线程基础 线程的创建和启动 继承Thread类来创建并启动 自定义Thread类的子类&#...
【洛谷 P1090】[NOIP... [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G ...
国民技术LPUART介绍 低功耗通用异步接收器(LPUART) 简介 低功耗通用异步收发器...
城乡供水一体化平台-助力乡村振... 城乡供水一体化管理系统建设方案 城乡供水一体化管理系统是运用云计算、大数据等信息化手段࿰...
程序的循环结构和random库...   第三个参数就是步长     引入文件时记得指明字符格式,否则读入不了 ...
中国版ChatGPT在哪些方面... 目录 一、中国巨大的市场需求 二、中国企业加速创新 三、中国的人工智能发展 四、企业愿景的推进 五、...
报名开启 | 共赴一场 Flu... 2023 年 1 月 25 日,Flutter Forward 大会在肯尼亚首都内罗毕...
汇编00-MASM 和 Vis... Qt源码解析 索引 汇编逆向--- MASM 和 Visual Studio入门 前提知识ÿ...
【简陋Web应用3】实现人脸比... 文章目录🍉 前情提要🌷 效果演示🥝 实现过程1. u...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
windows安装JDK步骤 一、 下载JDK安装包 下载地址:https://www.oracle.com/jav...
分治法实现合并排序(归并排序)... 🎊【数据结构与算法】专题正在持续更新中,各种数据结构的创建原理与运用✨...
在linux上安装配置node... 目录前言1,关于nodejs2,配置环境变量3,总结 前言...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
Linux内核进程管理并发同步... 并发同步并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理...
opencv学习-HOG LO... 目录1. HOG(Histogram of Oriented Gradients,方向梯度直方图)1...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...