Phát hiện và ghi lại âm thanh bằng Python


99

Tôi cần ghi lại các đoạn âm thanh dưới dạng tệp WAV mà sau đó tôi có thể chuyển sang một bit khác của python để xử lý. Vấn đề là mình cần xác định thời điểm có âm thanh rồi ghi lại, dừng lại khi nó im lặng rồi chuyển tệp đó vào mô-đun xử lý.

Tôi nghĩ rằng mô-đun sóng sẽ có thể phát hiện khi nào có sự im lặng thuần túy và loại bỏ nó sau đó ngay khi phát hiện thấy thứ gì đó không phải là khoảng lặng, hãy bắt đầu ghi, sau đó khi đường truyền im lặng lại dừng ghi.

Chỉ là tôi không thể hiểu được nó, bất cứ ai có thể giúp tôi bắt đầu với một ví dụ cơ bản.

Câu trả lời:


106

Tiếp theo câu trả lời của Nick Fortescue, đây là một ví dụ đầy đủ hơn về cách ghi âm từ micrô và xử lý dữ liệu kết quả:

from sys import byteorder
from array import array
from struct import pack

import pyaudio
import wave

THRESHOLD = 500
CHUNK_SIZE = 1024
FORMAT = pyaudio.paInt16
RATE = 44100

def is_silent(snd_data):
    "Returns 'True' if below the 'silent' threshold"
    return max(snd_data) < THRESHOLD

def normalize(snd_data):
    "Average the volume out"
    MAXIMUM = 16384
    times = float(MAXIMUM)/max(abs(i) for i in snd_data)

    r = array('h')
    for i in snd_data:
        r.append(int(i*times))
    return r

def trim(snd_data):
    "Trim the blank spots at the start and end"
    def _trim(snd_data):
        snd_started = False
        r = array('h')

        for i in snd_data:
            if not snd_started and abs(i)>THRESHOLD:
                snd_started = True
                r.append(i)

            elif snd_started:
                r.append(i)
        return r

    # Trim to the left
    snd_data = _trim(snd_data)

    # Trim to the right
    snd_data.reverse()
    snd_data = _trim(snd_data)
    snd_data.reverse()
    return snd_data

def add_silence(snd_data, seconds):
    "Add silence to the start and end of 'snd_data' of length 'seconds' (float)"
    silence = [0] * int(seconds * RATE)
    r = array('h', silence)
    r.extend(snd_data)
    r.extend(silence)
    return r

def record():
    """
    Record a word or words from the microphone and 
    return the data as an array of signed shorts.

    Normalizes the audio, trims silence from the 
    start and end, and pads with 0.5 seconds of 
    blank sound to make sure VLC et al can play 
    it without getting chopped off.
    """
    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT, channels=1, rate=RATE,
        input=True, output=True,
        frames_per_buffer=CHUNK_SIZE)

    num_silent = 0
    snd_started = False

    r = array('h')

    while 1:
        # little endian, signed short
        snd_data = array('h', stream.read(CHUNK_SIZE))
        if byteorder == 'big':
            snd_data.byteswap()
        r.extend(snd_data)

        silent = is_silent(snd_data)

        if silent and snd_started:
            num_silent += 1
        elif not silent and not snd_started:
            snd_started = True

        if snd_started and num_silent > 30:
            break

    sample_width = p.get_sample_size(FORMAT)
    stream.stop_stream()
    stream.close()
    p.terminate()

    r = normalize(r)
    r = trim(r)
    r = add_silence(r, 0.5)
    return sample_width, r

def record_to_file(path):
    "Records from the microphone and outputs the resulting data to 'path'"
    sample_width, data = record()
    data = pack('<' + ('h'*len(data)), *data)

    wf = wave.open(path, 'wb')
    wf.setnchannels(1)
    wf.setsampwidth(sample_width)
    wf.setframerate(RATE)
    wf.writeframes(data)
    wf.close()

if __name__ == '__main__':
    print("please speak a word into the microphone")
    record_to_file('demo.wav')
    print("done - result written to demo.wav")

17
Để làm cho điều này hoạt động trong Python 3, chỉ cần thay thế xrange bằng dải ô.
Ben Elgar

1
Ví dụ tuyệt vời! Thực sự hữu ích khi tôi cố gắng xoay quanh cách ghi lại giọng nói bằng Python. Tôi có một câu hỏi nhanh là liệu có cách nào để xác định khoảng thời gian của bản ghi không. Bây giờ nó ghi lại một từ? Tôi có thể chơi với nó và có khoảng thời gian kỷ lục là 10 giây không? Cảm ơn!
Swan87

Tính năng phát hiện và chuẩn hóa không chính xác vì chúng tính toán trên byte chứ không phải quần short. Bộ đệm đó sẽ phải được chuyển đổi thành một mảng numpy trước khi xử lý.
ArekBulski

Không xrangevà cũng không rangethực sự cần thiết trong add_silence(vì vậy nó đã biến mất ngay bây giờ). Tôi nghĩ Arek có thể có một cái gì đó ở đây - sự chuyển đổi từ im lặng sang 'từ' nghe quá giật cục. Tôi nghĩ rằng có những câu trả lời khác cũng giải quyết nó.
Tomasz Gandor

47

Tôi tin rằng mô-đun WAVE không hỗ trợ ghi, chỉ xử lý các tệp hiện có. Bạn có thể muốn xem PyAudio để thực sự ghi âm. WAV là định dạng tệp đơn giản nhất thế giới. Trong paInt16, bạn chỉ nhận được một số nguyên có dấu đại diện cho một cấp và càng gần 0 thì càng yên tĩnh. Tôi không thể nhớ liệu các tệp WAV là byte đầu tiên hay byte thấp, nhưng một cái gì đó như thế này sẽ hoạt động (xin lỗi, tôi thực sự không phải là một lập trình viên python:

from array import array

# you'll probably want to experiment on threshold
# depends how noisy the signal
threshold = 10 
max_value = 0

as_ints = array('h', data)
max_value = max(as_ints)
if max_value > threshold:
    # not silence

Mã PyAudio để ghi âm được lưu giữ để tham khảo:

import pyaudio
import sys

chunk = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
RECORD_SECONDS = 5

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS, 
                rate=RATE, 
                input=True,
                output=True,
                frames_per_buffer=chunk)

print "* recording"
for i in range(0, 44100 / chunk * RECORD_SECONDS):
    data = stream.read(chunk)
    # check for silence here by comparing the level with 0 (or some threshold) for 
    # the contents of data.
    # then write data or not to a file

print "* done"

stream.stop_stream()
stream.close()
p.terminate()

Cảm ơn Nick, Có lẽ phải nói rằng tôi cũng đang sử dụng portaudio để chụp ảnh, một chút tôi đang mắc kẹt là kiểm tra sự im lặng, làm thế nào để tôi có được mức độ trong phần dữ liệu?

Tôi đã thêm một số mã thực sự đơn giản chưa được kiểm tra ở trên, nhưng nó sẽ thực hiện công việc bạn muốn
Nick Fortescue

Phiên bản trước của tôi có một lỗi, không xử lý dấu hiệu đúng cách. Tôi đã sử dụng mảng hàm thư viện () để phân tích cú pháp đúng cách
Nick Fortescue

Định dạng tệp WAV là một bộ chứa, nó có thể chứa âm thanh được mã hóa qua các codec khác nhau (như GSM hoặc MP3), một số khác xa là 'đơn giản nhất thế giới'.
Jacek Konieczny

2
Tôi tin rằng tùy chọn "output = True" trong khi mở luồng là không cần thiết để ghi và hơn nữa, nó có vẻ gây ra "IOError: [Errno Input tràn] -9981" trên thiết bị của tôi. Nếu không, cảm ơn vì ví dụ mã, nó rất hữu ích.
Binus

19

Cảm ơn cryo cho phiên bản cải tiến mà tôi đã dựa trên mã đã thử nghiệm của mình bên dưới:

#Instead of adding silence at start and end of recording (values=0) I add the original audio . This makes audio sound more natural as volume is >0. See trim()
#I also fixed issue with the previous code - accumulated silence counter needs to be cleared once recording is resumed.

from array import array
from struct import pack
from sys import byteorder
import copy
import pyaudio
import wave

THRESHOLD = 500  # audio levels not normalised.
CHUNK_SIZE = 1024
SILENT_CHUNKS = 3 * 44100 / 1024  # about 3sec
FORMAT = pyaudio.paInt16
FRAME_MAX_VALUE = 2 ** 15 - 1
NORMALIZE_MINUS_ONE_dB = 10 ** (-1.0 / 20)
RATE = 44100
CHANNELS = 1
TRIM_APPEND = RATE / 4

def is_silent(data_chunk):
    """Returns 'True' if below the 'silent' threshold"""
    return max(data_chunk) < THRESHOLD

def normalize(data_all):
    """Amplify the volume out to max -1dB"""
    # MAXIMUM = 16384
    normalize_factor = (float(NORMALIZE_MINUS_ONE_dB * FRAME_MAX_VALUE)
                        / max(abs(i) for i in data_all))

    r = array('h')
    for i in data_all:
        r.append(int(i * normalize_factor))
    return r

def trim(data_all):
    _from = 0
    _to = len(data_all) - 1
    for i, b in enumerate(data_all):
        if abs(b) > THRESHOLD:
            _from = max(0, i - TRIM_APPEND)
            break

    for i, b in enumerate(reversed(data_all)):
        if abs(b) > THRESHOLD:
            _to = min(len(data_all) - 1, len(data_all) - 1 - i + TRIM_APPEND)
            break

    return copy.deepcopy(data_all[_from:(_to + 1)])

def record():
    """Record a word or words from the microphone and 
    return the data as an array of signed shorts."""

    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, output=True, frames_per_buffer=CHUNK_SIZE)

    silent_chunks = 0
    audio_started = False
    data_all = array('h')

    while True:
        # little endian, signed short
        data_chunk = array('h', stream.read(CHUNK_SIZE))
        if byteorder == 'big':
            data_chunk.byteswap()
        data_all.extend(data_chunk)

        silent = is_silent(data_chunk)

        if audio_started:
            if silent:
                silent_chunks += 1
                if silent_chunks > SILENT_CHUNKS:
                    break
            else: 
                silent_chunks = 0
        elif not silent:
            audio_started = True              

    sample_width = p.get_sample_size(FORMAT)
    stream.stop_stream()
    stream.close()
    p.terminate()

    data_all = trim(data_all)  # we trim before normalize as threshhold applies to un-normalized wave (as well as is_silent() function)
    data_all = normalize(data_all)
    return sample_width, data_all

def record_to_file(path):
    "Records from the microphone and outputs the resulting data to 'path'"
    sample_width, data = record()
    data = pack('<' + ('h' * len(data)), *data)

    wave_file = wave.open(path, 'wb')
    wave_file.setnchannels(CHANNELS)
    wave_file.setsampwidth(sample_width)
    wave_file.setframerate(RATE)
    wave_file.writeframes(data)
    wave_file.close()

if __name__ == '__main__':
    print("Wait in silence to begin recording; wait in silence to terminate")
    record_to_file('demo.wav')
    print("done - result written to demo.wav")

cảm ơn, hoạt động tốt. Trong trường hợp của tôi, tôi phải chỉnh sửa return copy.deepcopy(data_all[_from:(_to + 1)])thànhcopy.deepcopy(data_all[int(_from):(int(_to) + 1)])
lukassliacky

6
import pyaudio
import wave
from array import array

FORMAT=pyaudio.paInt16
CHANNELS=2
RATE=44100
CHUNK=1024
RECORD_SECONDS=15
FILE_NAME="RECORDING.wav"

audio=pyaudio.PyAudio() #instantiate the pyaudio

#recording prerequisites
stream=audio.open(format=FORMAT,channels=CHANNELS, 
                  rate=RATE,
                  input=True,
                  frames_per_buffer=CHUNK)

#starting recording
frames=[]

for i in range(0,int(RATE/CHUNK*RECORD_SECONDS)):
    data=stream.read(CHUNK)
    data_chunk=array('h',data)
    vol=max(data_chunk)
    if(vol>=500):
        print("something said")
        frames.append(data)
    else:
        print("nothing")
    print("\n")


#end of recording
stream.stop_stream()
stream.close()
audio.terminate()
#writing to file
wavfile=wave.open(FILE_NAME,'wb')
wavfile.setnchannels(CHANNELS)
wavfile.setsampwidth(audio.get_sample_size(FORMAT))
wavfile.setframerate(RATE)
wavfile.writeframes(b''.join(frames))#append frames recorded to file
wavfile.close()

Tôi nghĩ điều này sẽ hữu ích, nó là một script đơn giản sẽ kiểm tra xem có khoảng lặng hay không, nếu phát hiện ra khoảng lặng, nó sẽ không ghi, ngược lại nó sẽ ghi.


3

Trang web pyaudio có nhiều ví dụ khá ngắn gọn và rõ ràng: http://people.csail.mit.edu/hubert/pyaudio/

Cập nhật ngày 14 tháng 12 năm 2019 - Ví dụ chính từ trang web được liên kết ở trên từ năm 2017:


"""PyAudio Example: Play a WAVE file."""

import pyaudio
import wave
import sys

CHUNK = 1024

if len(sys.argv) < 2:
    print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
    sys.exit(-1)

wf = wave.open(sys.argv[1], 'rb')

p = pyaudio.PyAudio()

stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
                output=True)

data = wf.readframes(CHUNK)

while data != '':
    stream.write(data)
    data = wf.readframes(CHUNK)

stream.stop_stream()
stream.close()

p.terminate()

0

Bạn cũng có thể muốn nhìn vào csounds . Nó có một số API, bao gồm cả Python. Nó có thể tương tác với giao diện AD và thu thập các mẫu âm thanh.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.