FFmpeg将.mp3输出保存到变量中 [英] FFmpeg save .mp3 output into a variable
问题描述
在我的应用程序中,我想修改各种mp3,然后将它们混合在一起.我知道我可以使用FFmpeg中的单个命令行来完成此操作,但是由于我需要在每个样本上使用各种过滤器,并且其中有五个,因此最终可能会很混乱.我的想法是分别编辑每个样本,将它们保存到变量中,最后将它们混合.这是我的代码:
In my application I want to modify various mp3 and then mix them together. I know I could do it with a single command line in FFmpeg but It can end up very messy since I need to use various filter on each sample and I have five of them. My idea is to edit each sample individually, save them into a variable and finally mix them. This is my code:
import subprocess
def create_samp():
sample= subprocess.run(["ffmpeg", "-y", "-i", "https://freesound.org/data/previews/186/186942_2594536-hq.mp3", \
"-filter_complex", "adelay=15000|15000", "-codec:v", "copy", "-f", "mp3","-"], stdout=subprocess.PIPE)
return(sample)
def record(samp):
subprocess.run(["ffmpeg", "-y", "-i", "https://cdns-preview-b.dzcdn.net/stream/c-b0b684fe962f93dc43f1f7ea493683a1-3.mp3", \
"-i", samp.stdout, "-f", "-mp3", "copy", "output.mp3"])
samp = create_samp()
record(samp)
我的问题是我必须对 stdout
进行编码.我已经尝试过'utf-8'
,但收到此错误:
My issue is that I have to encode the stdout
. I've tried 'utf-8'
but got this error:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 45: invalid start byte
带有''utf-16':
With `'utf-16':
UnicodeDecodeError: 'utf-16-le' codec can't decode bytes in position 239454-239455: illegal encoding
为什么要解决此问题?我的方法正确吗?
Why is the way to fix this issue? Is my approach the right one?
由于@Rotem,我得以成功完成自己想做的事情.但是现在我面临另一个问题,因为我想混合多达5种声音,所以我尝试以一种懒惰/简单的方式实现它:
Thanks to @Rotem I succeed to do what I wanted to. But now I am facing an other issue, since I want to mix up to 5 sounds, I tried to implement it the lazy/easy way:
import subprocess
def create_samp_2():
sample= subprocess.run(["ffmpeg", "-i", "https://freesound.org/data/previews/186/186942_2594536-hq.mp3", \
"-af", "adelay=15000|15000", "-f", "mp3", "pipe:"], stdout=subprocess.PIPE).stdout
return(sample)
def create_samp():
sample= subprocess.run(["ffmpeg", "-i", "https://freesound.org/data/previews/370/370934_6399962-lq.ogg", \
"-af", "adelay=1000|1000", "-f", "mp3", "pipe:"], stdout=subprocess.PIPE).stdout
return(sample)
def record(samp, samp_2):
process = subprocess.Popen(["ffmpeg", "-y", '-f', 'mp3', \
"-i", "https://cdns-preview-b.dzcdn.net/stream/c-b0b684fe962f93dc43f1f7ea493683a1-3.mp3", \
"-i", "pipe:", \
"-i", "pipe:", \
"-filter_complex", "amix=inputs=3:duration=longest", "output.mp3"], stdin=subprocess.PIPE)
process.stdin.write(samp)
process.stdin.write(samp_2)
process.stdin.close()
process.wait()
samp = create_samp()
samp_2 = create_samp_2()
record(samp, samp_2)
令人惊讶的是,我的两个声音在正确的时间开始播放,但是第二个声音混乱了.因此,这不是正确的方法.
Surprisingly it works, my two sounds start at the right time, but the second sound is messed up. So it's not the right way to do it.
然后我尝试按照这种方式建议命名管道:
Then I tried named pipes as suggested this way:
"pipe1:"
但是我得到这个错误:
pipe1:: Protocol not found
Did you mean file:pipe1:?
阅读命名管道wiki时,我必须使用 mkfifo()
创建它们.
Reading named pipe wiki it is stated that I have to create them with mkfifo()
.
所以我尝试了:
import os
pipe1 = "pipe1"
def create_pipe1():
os.mkfifo(pipe1)
But now I have this error: pipe1:: Protocol not found
Did you mean file:pipe1:?
推荐答案
您的方法正确,但需要修复.
Your approach the right, but fixes are needed.
修复 create_samp()
:
- 您不需要
"-codec:v","copy"
参数,因为没有视频流.
- You don't need
"-codec:v", "copy"
arguments because there is no video stream.
修复记录(样本)
:
- 您不能使用
-i"
"samp.stdout"
,因为samp.stdout
是字节数组(Python子流程模块将其用作字符串). - 使用
-i","pipe:"
从stdin管道接收第二个音频. - 由于要混合两个音频流,因此必须使用
-filter_complex"
参数和amix
音频过滤器或amerge
音频过滤器,如此处所述. - 将
samp
写入stdin管道,然后关闭stdin管道.
- You can't use
"-i"
"samp.stdout"
, becausesamp.stdout
is a bytes array (Python subprocess module uses it as a string). - Use
"-i", "pipe:"
for receiving the the second audio from stdin pipe. - Since you want to mix two audio streams you have to use
"-filter_complex"
argument, andamix
audio filter oramerge
audio filter, as described here. - Write
samp
to stdin pipe, and close stdin pipe.
这是代码:
import subprocess
def create_samp():
# Read audio stream from https://freesound.org/data/previews/186/186942_2594536-hq.mp3
# Apply adelay audio filter.
# Encode the audio in mp3 format.
# FFmpeg output is passed to stdout pipe, and stored in sample bytes array.
sample= subprocess.run(["ffmpeg", "-i", "https://freesound.org/data/previews/186/186942_2594536-hq.mp3", \
"-af", "adelay=15000|15000", "-f", "mp3", "pipe:"], stdout=subprocess.PIPE).stdout
return(sample)
def record(samp):
# Open FFmpeg as sub-process
# Use two audio input streams:
# 1. WEB address
# 2. PIPE (the input is going to be written stdin pipe).
# Merge the two audio streams using amix audio filter.
# Store the result to output file: output.mp3
process = subprocess.Popen(["ffmpeg", "-y", '-f', 'mp3', \
"-i", "https://cdns-preview-b.dzcdn.net/stream/c-b0b684fe962f93dc43f1f7ea493683a1-3.mp3", \
"-i", "pipe:", \
"-filter_complex", "amix=inputs=2:duration=longest", "output.mp3"], stdin=subprocess.PIPE)
process.stdin.write(samp) # Write samp (bytes array containing mp3 data).
process.stdin.close() # Close stdin pipe.
process.wait() # Wait for FFmpeg sub-process to finish
samp = create_samp()
record(samp)
听起来不错...
使用命名管道(仅Linux):
当有两个以上的输入流(需要从内存缓冲区中传递)时,需要使用命名管道.
使用命名管道根本不是一件容易的事...
Named pipes are required when there are more than two input streams (that need to be piped from memory buffers).
Using named pipes is not trivial at all...
从FFmpeg的角度来看,命名管道就像(不可搜索的)输入文件.
From FFmpeg point of view named pipes are like (non-seekable) input files.
在Python中使用命名管道(在Linux中):
假设 pipe1
是命名管道"的名称,(例如 pipe1 ="audio_pipe1"
).
Using named pipes in Python (in Linux):
Assume pipe1
is the name of the "named pipe" (e.g pipe1 = "audio_pipe1"
).
-
创建一个命名管道":
Create a "named pipe":
os.mkfifo(pipe1)
以只写"方式打开管道;文件:
Open the pipe as "write only" file:
fd_pipe = os.open(pipe_name, os.O_WRONLY) # fd_pipe1 is a file descriptor (an integer)
将数据分成小块写入管道.
在大多数Linux系统中,管道的默认缓冲区大小为64KB.
由于数据大于65536字节,因此我们需要将数据分小块写入管道.
我决定使用1024字节的任意大小.
管道写入操作是阻塞"操作.操作.
我通过使用"writer"解决了这个问题.线程:
def writer(data, pipe_name, chunk_size):
# Open the pipes as opening files (open for "open for writing only").
fd_pipe = os.open(pipe_name, os.O_WRONLY) # fd_pipe1 is a file descriptor (an integer)
for i in range(0, len(data), chunk_size):
# Write to named pipe as writing to a file (but write the data in small chunks).
os.write(fd_pipe, data[i:chunk_size+i]) # Write 1024 bytes of data to fd_pipe
关闭管道:
Close the pipe:
os.close(fd_pipe)
删除(取消链接)命名管道:
Remove (unlink) the named pipe:
os.unlink(pipe1)
这是先前的示例,使用两个命名管道:
Here is the previous sample, using two named pipes:
import subprocess
import os
from threading import Thread
def create_samp():
# Read audio stream from https://freesound.org/data/previews/186/186942_2594536-hq.mp3
# Apply adelay audio filter.
# Encode the audio in mp3 format.
# FFmpeg output is passed to stdout pipe, and stored in sample bytes array.
sample1 = subprocess.run(["ffmpeg", "-i", "https://freesound.org/data/previews/186/186942_2594536-hq.mp3",
"-af", "adelay=15000|15000", "-f", "mp3", "pipe:"], stdout=subprocess.PIPE).stdout
# Read second audio sample from https://cdns-preview-b.dzcdn.net/stream/c-b0b684fe962f93dc43f1f7ea493683a1-3.mp3
sample2 = subprocess.run(["ffmpeg", "-i", "https://cdns-preview-b.dzcdn.net/stream/c-b0b684fe962f93dc43f1f7ea493683a1-3.mp3",
"-f", "mp3", "pipe:"], stdout=subprocess.PIPE).stdout
return sample1, sample2
def writer(data, pipe_name, chunk_size):
# Open the pipes as opening files (open for "open for writing only").
fd_pipe = os.open(pipe_name, os.O_WRONLY) # fd_pipe1 is a file descriptor (an integer)
for i in range(0, len(data), chunk_size):
# Write to named pipe as writing to a file (but write the data in small chunks).
os.write(fd_pipe, data[i:chunk_size+i]) # Write 1024 bytes of data to fd_pipe
# Closing the pipes as closing files.
os.close(fd_pipe)
def record(samp1, samp2):
# Names of the "Named pipes"
pipe1 = "audio_pipe1"
pipe2 = "audio_pipe2"
# Create "named pipes".
os.mkfifo(pipe1)
os.mkfifo(pipe2)
# Open FFmpeg as sub-process
# Use two audio input streams:
# 1. Named pipe: "audio_pipe1"
# 2. Named pipe: "audio_pipe2"
# Merge the two audio streams using amix audio filter.
# Store the result to output file: output.mp3
process = subprocess.Popen(["ffmpeg", "-y", '-f', 'mp3',
"-i", pipe1,
"-i", pipe2,
"-filter_complex", "amix=inputs=2:duration=longest", "output.mp3"],
stdin=subprocess.PIPE)
# Initialize two "writer" threads (each writer writes data to named pipe in chunks of 1024 bytes).
thread1 = Thread(target=writer, args=(samp1, pipe1, 1024)) # thread1 writes samp1 to pipe1
thread2 = Thread(target=writer, args=(samp2, pipe2, 1024)) # thread2 writes samp2 to pipe2
# Start the two threads
thread1.start()
thread2.start()
# Wait for the two writer threads to finish
thread1.join()
thread2.join()
process.wait() # Wait for FFmpeg sub-process to finish
# Remove the "named pipes".
os.unlink(pipe1)
os.unlink(pipe2)
sampl1, sampl2 = create_samp()
record(sampl1, sampl2)
该代码已在Ubuntu 18.04(在虚拟机中)中进行了测试.
The code was tested in Ubuntu 18.04 (in a virtual machine).
这篇关于FFmpeg将.mp3输出保存到变量中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!