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

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_channels
        out_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 main

import (
	"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 = 192000

func 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.AVCodecContext
	pkt := libavcodec.AvPacketAlloc()
	frame := libavutil.AvFrameAlloc()

	aStreamIndex := -1

	for {
		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)).Codecpar
		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
		}

		codecCtx.PktTimebase = fmtCtx.GetStream(uint32(aStreamIndex)).TimeBase

		if codecCtx.AvcodecOpen2(codec, nil) < 0 {
			fmt.Printf("Cannot open audio codec.\n")
			return
		}

		//设置转码参数
		out_channel_layout := codecCtx.ChannelLayout
		out_sample_fmt := libavutil.AV_SAMPLE_FMT_S16
		out_sample_rate := int32(44100) //codecCtx.SampleRate
		out_channels := libavutil.AvGetChannelLayoutNbChannels(out_channel_layout)

		audio_out_buffer := libavutil.AvMalloc(MAX_AUDIO_FRAME_SIZE * 2)

		var swr_ctx *libswresample.SwrContext
		swr_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_buffer
							for 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格式的文件。这对于音视频应用开发和研究等领域非常有帮助。在实际工作中,我们可以根据具体需求和场景,进一步优化和扩展相关功能。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++学习】类和对象(中)一招带你彻底了解六大默认成员函数

前言&#xff1a;在之前&#xff0c;我们对类和对象的上篇进行了讲解&#xff0c;今天我们我将给大家带来的是类和对象中篇的学习&#xff0c;继续深入探讨【C】中类和对象的相关知识&#xff01;&#xff01;&#xff01; 目录 1. 类的6个默认成员函数 2. 构造函数 2.1概念介…

【黑马JVM(2)】垃圾回收

JVM垃圾回收如何判断对象可以回收引用计数法可达性分析算法四种引用垃圾回收算法标记-清除标记-整理标记-复制分代垃圾回收相关VM参数垃圾回收器串行吞吐量优先响应时间优先G1垃圾回收阶段Young CollectionYoung Collection跨代引用Young CollectionCMRemark-SATBMixed Collect…

第三代api自动化测试框架使用教程(pytest+allure+sql+yaml)

使用教程一、配置1、环境配置2、框架配置3、启动入口二、用例编写1、用例模板2、参数依赖写法2、函数&#xff08;方法插件&#xff09;写法3、接口上传文件和表单参数4、接口上传json参数5、接口无数据填写6、code断言7、body断言7、json断言8、sql断言9、完整断言写法&#x…

TCP UDP详解

文章目录TCP UDP协议1. 概述2. 端口号 复用 分用3. TCP3.1 TCP首部格式3.2 建立连接-三次握手3.3 释放连接-四次挥手3.4 TCP流量控制3.5 TCP拥塞控制3.6 TCP可靠传输的实现3.7 TCP超时重传4. UDP5.TCP与UDP的区别TCP UDP协议 1. 概述 TCP、UDP协议是TCP/IP体系结构传输层中的…

手把手的教你安装PyCharm --Pycharm安装详细教程(一)(非常详细,非常实用)

简介 Jetbrains家族和Pycharm版本划分&#xff1a; pycharm是Jetbrains家族中的一个明星产品&#xff0c;Jetbrains开发了许多好用的编辑器&#xff0c;包括Java编辑器&#xff08;IntelliJ IDEA&#xff09;、JavaScript编辑器&#xff08;WebStorm&#xff09;、PHP编辑器&…

C/C++考试必考题目(含答案*仅供参考)

今天继续来分享几个C经常考试的几道题目&#xff0c;大家快快拿去&#xff0c;赶紧做一下 目录 &#xff08;小事一桩&#xff09;约瑟夫问题 discreb input output 效果展示&#xff1a; 1、 猜价格游戏 2、 计算 N 以内的所有素数 3、 袋中取球 4、 乘法口诀表 …

尚医通-(三十三)就诊人管理功能实现

目录&#xff1a; &#xff08;1&#xff09;前台用户系统-就诊人管理-需求说明 &#xff08;2&#xff09;就诊人管理-接口开发-列表接口 &#xff08;3&#xff09;就诊人管理-接口开发-其他接口 &#xff08;4&#xff09;前台用户系统-就诊人管理-前端整合 &#xff0…

react的基础使用

react中为什么使用jsxReact 认为渲染逻辑本质上与其他 UI 逻辑内在耦合&#xff0c;比如&#xff0c;在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI&#xff0c;以及需要在 UI 中展示准备好的数据。react认为将业务代码和数据以及事件等等 需要和UI高度耦合…

竞赛无人机搭积木式编程——以2022年TI电赛送货无人机一等奖复现为例学习(7月B题)

在学习本教程前&#xff0c;请确保已经学习了前4讲中无人机相关坐标系知识、基础飞行控制函数、激光雷达SLAM定位条件下的室内定点控制、自动飞行支持函数、导航控制函数等入门阶段的先导教程。 同时用户在做二次开发自定义的飞行任务时&#xff0c;可以参照第5讲中2021年国赛植…

【uniapp小程序实战】—— 使用腾讯地图获取定位

文章目录&#x1f34d;前言&#x1f34b;正文1、首先看官网uni.getLocation(OBJECT)#注意2、腾讯位置服务平台申请密钥和下载SDK2.1 申请开发者秘钥2.2 开通webserviceAPI服务2.3 下载微信小程序JavaScriptSDK2.4 安全域名设置3、配置manifest.json文件4、示例代码展示4.1 引用…

面试重难点问题(C++)

持续更新&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 网络部分 1.问&#xff0c;四次挥手的过程&#xff0c;和双方状态变化&#xff1f; 挥手这前&#xff0c;两边都是established状态&#xff0c;客户端发起断开请求&#xff0c;向服务器发送fin请求&…

Docker6种网络配置详解,网络模式应该这么选

文章目录一、Bridge网络模式二、Host网络模式三、Overlay网络模式四、None网络模式五、Macvlan网络模式六、Ipvlan网络模式七、网络模式选择在Docker中&#xff0c;网络配置是一个重要的主题&#xff0c;因为容器需要与其他容器或外部网络进行通信。Docker提供了多种网络模式和…

注意下C语言整形提升

C语言整形提升 C语言整形提升是指在表达式中使用多种类型的数据时&#xff0c;编译器会自动将较小的类型转换为较大的类型&#xff0c;以便进行运算。在C语言中&#xff0c;整型提升规则如下&#xff1a; 如果表达式中存在short类型&#xff0c;则将其自动转换为int类型。 如…

【JavaEE】初识线程

一、简述进程认识线程之前我们应该去学习一下“进程" 的概念&#xff0c;我们可以把一个运行起来的程序称之为进程&#xff0c;进程的调度&#xff0c;进程的管理是由我们的操作系统来管理的&#xff0c;创建一个进程&#xff0c;操作系统会为每一个进程创建一个 PCB&…

C++之深浅拷贝

一、浅拷贝 我们看下以下代码 Test.h 文件 #pragma once #include<iostream> using namespace std; class Student { public:Student(){}~Student(){if (m_Id ! nullptr){delete m_Id;m_Id nullptr;}}Student(int id, string strName){m_Id new int[id];m_strName s…

字符函数和字符串函数(上)-C语言详解

CSDN的各位友友们你们好,今天千泽为大家带来的是C语言中字符函数和字符串函数的详解,掌握了这些内容能够让我们更加灵活的运用字符串,接下来让我们一起走进今天的内容吧!写这篇文章需要在cplusplus.com上大量截图,十分不易!如果对您有帮助的话希望能够得到您的支持和帮助,我会持…

信号处理-小波变换4-DWT离散小波变换概念及离散小波变换实现滤波

连续小波变换的适用场景&#xff1a;能够获取某一段信号的瞬时信息、时频信息 缺点&#xff1a;计算量大&#xff0c;无法进行数据压缩- 针对连续小波存在的缺点提出离散小波变换 离散小波变换 离散小波变换 分解过程&#xff1a;&#xff08;离散2进正交&#xff09; cD1: …

数据结构与算法——栈和队列<也不过如此>

&#x1f3c6;作者主页&#xff1a;king&南星 &#x1f384;专栏链接&#xff1a;数据结构 &#x1f3c5;文章目录一、&#x1f947;栈1、&#x1f948;概念理解2、&#x1f948;链表头插头删实现栈1、&#x1f949;预备准备2、&#x1f949;创建结点函数3、&#x1f949;遍…

SPI读写SD卡速度有多快?

SD卡是一个嵌入式中非常常用的外设&#xff0c;可以用于存储一些大容量的数据。但用单片机读写SD卡速度一般都有限&#xff08;对于高速SD卡&#xff0c;主要是受限于单片机本身的接口速度&#xff09;&#xff0c;在高速、实时数据存储时可能会有影响。但具体速度可以达到多少…

vue2+高德地图web端开发使用

创建vue2项目我们创建一个vue2项目&#xff0c;创建vue2项目就不用再多说了吧&#xff0c;使用“vue create 项目名 ”创建即可注册高德地图高德地图官网地址&#xff1a;https://lbs.amap.com/如果是第一次使用&#xff0c;点击注册然后进入我们的控制台注册完之后进入控制台&…