每天叫醒我的不是理想,是楼下广场舞的音乐。

音乐是人类的通用语言,不分国界不分种族。
抖音短视频爆火的关键因素之一,就是普通人也能便捷地使用BGM表达自我。
从感性角度看,音乐可以有很多种解释,如:

  • 音乐是有逻辑的声音。
  • 音乐是以声音和时间为材料的艺术。
  • 音乐是思想情感的表达,是精神的延续。
  • ……

而从数学角度看,音乐就是时间和频率的关系

声音的本质是波,人类听觉的原理就是波引起了耳朵鼓膜的振动。
人们用不同乐器、不同力度,在一段连续时间里敲击,就组合出了时间和频率的关系。
一切物体都有自己的频率,所以整个世界也可以理解为一篇乐章。
当音乐被计算机数字化后,我们就可以用文件的形式保存它。但现实世界的声音是连续的,而计算机世界是离散(由0和1组成)的。想要用计算机捕捉声音,就得把连续信息转为离散的数据,这个过程就是信号的“模数转化”。

处理过程中最关键的参数就是“采样率”,即每秒钟用多少份数据表达声音信号。此外每份数据大小以及声道数,与采样率一起,决定了保存后声音和原声间的差距。

和图像一样,音乐也有很多种压缩算法。所谓“无损音乐”,就是确保源文件信息不丢失情况下压缩数据,常见格式如flacapewav;更常见的音乐格式是mp3,是一种有损压缩格式,虽然老旧,但依旧流行。

Python标准模块wave支持wav文件读写,但涉及到压缩算法时,都需要借助外部模块。其中功能最全也最流行的就是ffmpeg,它是开源视频处理软件,支持绝大多数的音视频格式编码,被广泛引用于各大视频网站和商业软件。

ffmpeg

ffmpeg安装

ffmpeg需要独立安装,网上大部分教程都已过时,最好参考官方文档。

比如在MacOS上通过Homebrew的安装方法:

  1. brew install ffmpeg
  2. brew tap homebrew-ffmpeg/ffmpeg
  3. brew install homebrew-ffmpeg/ffmpeg/ffmpeg

以上就是ffmepg的完全安装,也可以根据自己需要定制选项:

  1. 查看可选项:brew options homebrew-ffmpeg/ffmpeg/ffmpeg
  2. 安装关联选项:brew install homebrew-ffmpeg/ffmpeg/ffmpeg --with-libvorbis --with-sdl2 --with-theora

安装完后,通过ffprobe可以获取音视频文件的详细信息:ffprobe -i 音视频文件

7、音频文件 - 图1

ffmpeg基本使用

通过ffmpeg命令可以对音视频文件进行格式转换、拼接、切割、放大缩小、提取图片/音频/字幕等操作。

ffmpeg的命令格式:

  1. $ ffmpeg [全局选项] {[输入文件选项] -i 输入_url_地址} {[输出文件选项] 输出_url_地址} ...

其中,常用命令行参数如下:

  • -c:指定编码器
  • -c copy:直接复制不编码更快
  • -c:v:指定视频编码器
  • -c:a:指定音频编码器
  • -i:指定输入文件
  • -an:去除音频流
  • -vn: 去除视频流
  • -preset:指定输出视频质量

使用ffmpeg -formats可列出支持的文件格式。

比如,想要转换音视频文件格式:

  1. $ ffmpeg -i video.mp4 video.avi

比如要从视频里提取音频:

  1. $ ffmpeg -i input.mp4 -vn output.mp3

音视频几乎所有的基本剪辑操作都可以用ffmpeg完成。ffmpeg养活了不少视频剪辑软件公司。

音频处理场景

如果仅仅是需要批量转格式,或者按固定标准剪辑音视频,ffmpeg足够应付,最多就是多些几行shell命令,比如增加个循环实现批量文件处理。

但如果涉及到对音视频内容处理,如实现视频效果、提取音频高潮等场景,就需要借助三方模块了。

Python处理音频数据等常见模块有2个:

  • librosa,擅长音频信号处理,内部用numpy存储数据,读写文件依赖soundfile模块(不支持mp3)。
  • pydub,底层基于ffmpeg读写文件,代码简洁,支持切割、格式转换、音量、ID3等常用功能,门槛低。

模块安装:

  • pip install librosa
  • pip install pydub

使用建议:日常用pydub足够应付,更强大的信号处理则需要librosa,但有一定数学门槛,需要了解信号处理原理,掌握傅立叶变换等基本算法。

本文重点介绍pydub模块使用,包括如下常见音频处理场景:

  • 基本操作:切割、合并、音量增减、过渡效果
  • 提取背景音乐:从视频提取音频、说话声与背景音乐分离
  • 提取音乐高潮
  • 语音智能处理:语音识别、合成、克隆

基本操作

pydub最核心的类是AudioSegment,几乎包含所有基本操作。

pydub基本文件读写

  1. import pathlib
  2. import pydub
  3. from pydub import AudioSegment
  4. from pydub.utils import mediainfo
  5. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/007audio')
  6. mp3_path = path.joinpath('1.mp3')
  7. out_path = path.joinpath('007audio_pydub_export.mp3')
  8. # 默认采样率44100Hz
  9. snd = AudioSegment.from_mp3(mp3_path)
  10. info = mediainfo(mp3_path)
  11. print(info) # ID3信息
  12. print(snd.duration_seconds, snd.frame_rate) # 音频时长,采样率
  13. snd.export(out_path, format='mp3', bitrate='32k') # 转成mp3格式

切割、合并、音量、过渡效果

  1. import pathlib
  2. from pydub import AudioSegment
  3. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/007audio')
  4. mp3_path = path.joinpath('1.mp3')
  5. cover_path = path.joinpath('cover.jpg')
  6. snd = AudioSegment.from_mp3(mp3_path)
  7. # 获取时长、分贝数、采样率
  8. print(snd.duration_seconds, snd.dBFS, snd.frame_rate)
  9. # 剪30秒核心片段,单位为毫秒
  10. snd_mid = snd[120000:150000]
  11. # 调大音量,单位分贝
  12. snd_mid += 6
  13. # 剪最后30秒片段
  14. snd_end = snd[-30000:]
  15. # 淡入淡出效果
  16. snd_final = snd_mid.append(snd_end, crossfade=1500)
  17. # 重复一次
  18. snd_final *= 2
  19. # 淡入淡出效果
  20. snd_final.fade_in(2000).fade_out(2000)
  21. # 加上原始信息
  22. tags={'artist': '程一初', 'album': '只差一个程序员了', 'comments': 'Come to Python1024!'}
  23. # 导出文件
  24. snd_final.export(path.joinpath('007audio_pydub_cut.mp3'), format='mp3', tags=tags, cover=str(cover_path))

提取背景音乐

如果只是提取视频里的音频,比较简单,分离保存即可。
但如果是想从音频里消除人声,只要背景音乐,就需要对音频内容做些处理。

一个假设:

一般背景音乐的左右声道不同,这样才有立体声效果;而人声左右声道相同。

所以,就像图像的去水印算法,我们可以用左声道叠加右声道的“反相”来消除人声,保留背景音乐。

“反相”是一种信号处理基本方式,对声音波中相位数据的操作。

  1. import pathlib
  2. from pydub import AudioSegment
  3. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/007audio')
  4. # 提取音频
  5. mp4_path = path.joinpath('2.mp4')
  6. cover_path = path.joinpath('cover.jpg')
  7. snd = AudioSegment.from_file(mp4_path)
  8. tags={'artist': '程一初', 'album': '只差一个程序员了', 'comments': 'Come to Python1024!'}
  9. snd.export(path.joinpath('007audio_pydub_from_video.mp3'), format='mp3', tags=tags, cover=str(cover_path))
  10. # 提取背景音乐
  11. mp4_path = path.joinpath('3.mp4')
  12. snd = AudioSegment.from_file(mp4_path)
  13. snd_l, snd_r = snd.split_to_mono()
  14. snd_r_inv = snd_r.invert_phase() # 反相
  15. bg_music = snd_l.overlay(snd_r_inv) # 覆盖后获得背景音乐,但有噪音
  16. bg_music.export(path.joinpath('007audio_pydub_split_music.mp3'), format='mp3')

当然,也可以用ffmpeg命令实现,只不过不那么容易读懂。

  1. $ ffmpeg -i 3.mp3 -af pan="stereo|c0=c0|c1=-1*c1" -ac 1 007audio_ffmpeg_bgmusic.mp3

这种算法只能针对符合假设的音频生效,如果需要更通用的场景,可以借助智能算法。

比如 spleeter就是一个利用已有机器训练模型来分离声音信号的模块,具体使用时支持3种声音分离模式:

  • 2stems:人声和其他声音
  • 4stems:人声、贝斯、鼓和其他声音
  • 5stems:人声、贝斯、鼓、钢琴和其他声音

模块安装:pip install spleeter,注意:spleeter依赖numba的0.48版本。
安装好之后,可以直接用命令开始分离:

  1. $ spleeter separate -i input.mp3 -p spleeter:2stems -o output

spleeter会自动下载对应的训练模型到当前目录的pretrained_models中,然后把分离后的音频保存在output文件夹内。

当然我们也可以在Python程序中调用:

  1. import pathlib
  2. from spleeter.separator import Separator
  3. from spleeter.audio.adapter import get_default_audio_adapter
  4. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/007audio')
  5. mp3_path = path.joinpath('3.mp3')
  6. out_path = path.joinpath('007audio_spleeter_out')
  7. separator = Separator('spleeter:2stems')
  8. audio_adapter = get_default_audio_adapter()
  9. separator.separate_to_file(
  10. str(mp3_path),
  11. out_path,
  12. audio_adapter=audio_adapter,
  13. synchronous=False)

spleeter对于基本声音分离应用足够应付,但如果需求更复杂,或者对精准度要求更高,则需要借助一些平台API实现,毕竟平台会用更多数据训练出更精准的模型。

提取音乐高潮

短视频之所以火,其中一大因素就是让人“爽”的背景音乐,剪辑中经常需要提取音乐的高潮部分。
怎样识别音乐的高潮部分呢?音乐高潮最普遍也是最简单的特征就是:多次循环。
按这样的思路,可以从整首歌中,找出重复次数最多、间隔最长的片段。但真正做的时候,需要对音频信号处理有一定基础。
可以参考pychorus项目。该项目基于librosa实现了重复音频片段识别算法。
模块安装:pip install pychorus,使用如下:

  1. import pathlib
  2. from pychorus import find_and_output_chorus
  3. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/007audio')
  4. mp3_path = path.joinpath('1.mp3')
  5. out_path = path.joinpath('007audio_chorus_out.wav')
  6. chorus_start_sec = find_and_output_chorus(mp3_path, out_path, 30)
  7. print(chorus_start_sec)

语音智能处理

日常生活和工作中,我们经常碰到这样的情况:

  • 在线学习课程时,希望有文字版,而不是拖沓的视频。
  • 录制短视频,配音很繁琐,有时嗓子不舒服还影响进度。
  • 想把短视频的解说语音换成自己喜欢的。

场景背后,分别对应不同的音频处理技术:

  • 语音识别:从音频变文字。
  • 语音合成:从文字变音频。
  • 语音克隆:训练某人声音模型。

目前主流技术都是人工智能算法的应用。
训练模型需要海量数据和计算,但应用起来相对方便,就像之前我们利用已有训练模型去识别人脸。
目前最方便的实现方式,是利用大平台的API接口,有些平台还能提供类似“语音情绪识别”等高阶功能。
目前部分平台还提供这类接口的试用,相对成熟的如阿里云、百度AI、科大讯飞。
我们以阿里云为例,体验语音识别语音合成

首先准备基本的账号环境:

  1. 开通阿里云账号
  2. 开通智能语音交互服务,选择“免费试用”
  3. 开通OSS对象存储服务,用于上传文件到公网(可选)
  4. 创建一个身份凭证,用来访问阿里云资源。

接着,准备语音识别的环境:

  1. 在控制台创建语音智能交互项目,获得app_key
  2. 场景选择“通用”、“中文普通话”、“16K采样率”,并发布上线
  3. 安装阿里云SDK:pip install aliyun-python-sdk-core

准备好后,就能用access_key_idaccess_key_secretapp_key调用语音服务了。

语音识别(Speech To Text,STT)

语音识别,就是把语音转为文字。视频可以提取语音后处理。

处理流程主要是3个步骤:

  1. 准备好录音文件,转为16K采样率的wav文件,上传到公网。
  2. 创建AcsClient实例,用access_key_idaccess_key_secret授权,构建服务请求,获取任务ID。
  3. 凭任务ID去查询阿里云处理情况,收取结果。

一些注意点:

  • 阿里云目前只支持16K/8K采样率、大小不超过10M的wavmp3格式音频文件。
  • 免费用户每日可识别不超过2小时的录音文件。
  • 音频文件需要公网能访问,本例中使用阿里云OSS访问。
  • OSS上传可在控制台操作,也可通过OSS2模块,模块安装:pip install oss2

第一步:用oss上传文件到公网:

  1. import pathlib
  2. import time
  3. import json
  4. from pydub import AudioSegment
  5. import oss2
  6. from aliyunsdkcore.client import AcsClient
  7. from aliyunsdkcore.request import CommonRequest
  8. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/007audio')
  9. mp3_path = path.joinpath('3.mp3')
  10. out_path = path.joinpath('3_16k.wav')
  11. out_txt_path = path.joinpath('007audio_stt.txt')
  12. ACK_ID = '<你的access_key_id>'
  13. ACK_SEC= '<你的access_key_secret>'
  14. APP_KEY= '<你的app_key>'
  15. bucket_path = pathlib.Path('https://python1024sh.oss-cn-shanghai.aliyuncs.com')
  16. # 把文件转为16K采样率的mp3,只用一个声道
  17. audio = AudioSegment.from_mp3(mp3_path)
  18. mono = audio.set_frame_rate(16000).set_channels(1)
  19. mono.export(out_path, format='wav', codec='pcm_s16le')
  20. auth = oss2.Auth(ACK_ID, ACK_SEC) # 鉴权
  21. bucket = oss2.Bucket(auth, 'https://oss-cn-shanghai.aliyuncs.com', 'python1024sh')
  22. obj_name = f'demo/{file_path.name}'
  23. res = bucket.put_object_from_file(obj_name, out_path)
  24. file_url = f'https://python1024sh.oss-cn-shanghai.aliyuncs.com/{obj_name}' # 公网路径
  25. print(file_url)

第二步:构建服务请求

  1. client = AcsClient(ACK_ID, ACK_SEC, 'cn-shanghai')
  2. post_req = CommonRequest()
  3. post_req.set_domain('filetrans.cn-shanghai.aliyuncs.com')
  4. post_req.set_version('2018-08-17')
  5. post_req.set_product('nls-filetrans')
  6. post_req.set_action_name('SubmitTask')
  7. post_req.set_method('POST')
  8. task = {'appkey': APP_KEY, 'file_link': file_url, 'version': '4.0', 'enable_words': False}
  9. print(json.dumps(task))
  10. post_req.add_body_params('Task', json.dumps(task))
  11. task_id = ''
  12. try:
  13. post_res = client.do_action_with_exception(post_req)
  14. post_res = json.loads(post_res)
  15. status_txt = post_res['StatusText']
  16. if status_txt == 'SUCCESS':
  17. task_id = post_res['TaskId']
  18. print (f'录音文件识别请求成功响应,task_id: {task_id}')
  19. else:
  20. print(f'录音文件识别请求失败: {status_txt}')
  21. except Exception as e:
  22. print(e)

第三步:查询任务结果

  1. get_req = CommonRequest()
  2. get_req.set_domain('filetrans.cn-shanghai.aliyuncs.com')
  3. get_req.set_version('2018-08-17')
  4. get_req.set_product('nls-filetrans')
  5. get_req.set_action_name('GetTaskResult')
  6. get_req.set_method('GET')
  7. get_req.add_query_param('TaskId', task_id)
  8. status_txt = ''
  9. while True:
  10. try:
  11. get_res = client.do_action_with_exception(get_req)
  12. get_res = json.loads(get_res)
  13. status_txt = get_res['StatusText']
  14. if status_txt in ['RUNNING', 'QUEUEING']:
  15. time.sleep(10) # 避免请求过于频繁
  16. else:
  17. break
  18. except Exception as e:
  19. print(e)
  20. if status_txt == 'SUCCESS':
  21. result = get_res["Result"]
  22. sentences = result["Sentences"]
  23. txt_list = [ s['Text'] for s in sentences]
  24. with open(out_txt_path, 'a') as f:
  25. f.writelines('\n'.join(txt_list))
  26. else:
  27. print(f'录音文件识别失败, {status_txt}')

语音合成(Text To Speech,TTS)

目前不少短视频,都用了语音合成技术,可以选择发音人来朗读准备好的文稿。
TTS是自然语言处理技术的一种,阿里云提供了alibabacloud-nls-python-sdk模块方便调用服务。
不过该模块并未上传到PyPI,就不能用pip安装,可以从官网下载后手动安装。
下载后进入目录执行:python setup.py install即可。

处理流程也是3个步骤:

  1. 获取服务访问的凭证(Token)。
  2. 准备好回调函数,用来接收生成的音频文件。
  3. 创建并发送任务请求,等待远端执行完毕。

一些注意点:

  • “回调函数”就好比你去吃饭,付完款在一旁等叫号。
  • TTS服务用Token鉴权,要为子账号添加NLS服务访问权限。
  • 传入文本不能超300字符,超过会被截断。
  • 长文本另有接口,最高10万字,提供restful接口。
  1. import pathlib
  2. import json
  3. from aliyunsdkcore.client import AcsClient
  4. from aliyunsdkcore.request import CommonRequest
  5. import ali_speech
  6. from ali_speech.callbacks import SpeechSynthesizerCallback
  7. from ali_speech.constant import TTSFormat
  8. from ali_speech.constant import TTSSampleRate
  9. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/007audio')
  10. out_path = path.joinpath('007audio_stt.wav')
  11. ACK_ID = '<你的access_key_id>'
  12. ACK_SEC= '<你的access_key_secret>'
  13. APP_KEY= '<你的app_key>'
  14. # 开始获取Token
  15. acs_client = AcsClient(ACK_ID, ACK_SEC, 'cn-shanghai')
  16. req = CommonRequest()
  17. req.set_method('POST')
  18. req.set_domain('nls-meta.cn-shanghai.aliyuncs.com')
  19. req.set_version('2019-02-28')
  20. req.set_action_name('CreateToken')
  21. res = acs_client.do_action_with_exception(req)
  22. res = json.loads(res)
  23. token = res['Token']['Id']
  24. class AudioCallback(SpeechSynthesizerCallback):
  25. # 回调函数,接受服务端数据返回
  26. def __init__(self, name):
  27. self._name = name
  28. self._fout = open(name, 'wb')
  29. def on_binary_data_received(self, raw):
  30. self._fout.write(raw)
  31. def on_completed(self, message):
  32. print('Completed: %s' % message)
  33. self._fout.close()
  34. def on_task_failed(self, message):
  35. print('Failed,task_id:%s, status_text:%s' % (
  36. message['header']['task_id'], message['header']['status_text']))
  37. self._fout.close()
  38. callback = AudioCallback(out_path)
  39. # 开始发送任务请求
  40. text = "程一初发表的文章,在公众号“只差一个程序员了”。"
  41. client = ali_speech.NlsClient()
  42. client.set_log_level('INFO')
  43. synthesizer = client.create_synthesizer(callback)
  44. synthesizer.set_appkey(APP_KEY)
  45. synthesizer.set_token(token)
  46. synthesizer.set_voice('Siqi')
  47. synthesizer.set_text(text)
  48. synthesizer.set_format(TTSFormat.WAV)
  49. synthesizer.set_sample_rate(TTSSampleRate.SAMPLE_RATE_16K)
  50. synthesizer.set_volume(50)
  51. synthesizer.set_speech_rate(0)
  52. synthesizer.set_pitch_rate(0)
  53. try:
  54. ret = synthesizer.start()
  55. if ret < 0:
  56. print(ret)
  57. else:
  58. synthesizer.wait_completed()
  59. except Exception as e:
  60. print(e)
  61. finally:
  62. synthesizer.close()

总结

本文介绍了音频的实用处理方法,包括ffmpegpydub的基本使用,分离提取背景音乐、识别音乐高潮的方法,以及常见智能语音服务使用。

其中ffmpeg在视频处理方面能发挥更大作用,下一章我会重点介绍视频处理。

入群学习

7、音频文件 - 图2