如何录制浏览器播放的音频?虚拟音频线与Python采集步骤

如何录制浏览器播放的音频?虚拟音频线与Python采集步骤

一、为什么要录制浏览器音频?

  1. 直接录音的问题:使用普通麦克风录制电脑播放的音频会引入环境噪音,音质差
  2. 版权保护限制:许多流媒体平台禁止直接下载内容
  3. 内容保存需求:你可能想要保存在线课程、会议录音或喜欢的音乐
  4. 音频处理需求:需要对网络音频进行编辑、转录或分析

二、技术原理

1. 虚拟音频线 (Virtual Audio Cable)

想象一下,你的电脑有真实的音频线连接扬声器和麦克风。虚拟音频线在软件层面创建了这样的"虚拟线路",允许将一个应用程序的音频输出直接"路由"到另一个应用程序的音频输入,就像用一根虚拟的线将它们连接起来。

2. Python音频采集

使用Python的PyAudio库直接从系统音频输入(现在变成了虚拟音频线的输出)捕获音频数据,并保存为文件。

三、完整操作步骤

1、安装虚拟音频线 (VAC)

1.1、为什么需要VAC?

Windows系统默认不会让应用程序直接捕获另一个应用程序的音频输出。VAC创建了一个虚拟的音频设备,可以将浏览器音频"重定向"到Python程序可以访问的地方。

1.2、如何安装
  1. 访问 Virtual Audio Cable 官网
  2. 下载适合你系统的版本(32位或64位)
  3. 运行安装程序,按照提示完成安装
  4. 安装后可能需要重启电脑
1.3、安装验证
  • 安装完成后,在系统声音设置中会出现新的音频设备
  • 这些设备通常命名为"Virtual Audio Cable"

2、在浏览器中打开你想要录制的音频内容

3、配置音频路由

  1. 打开音量合成器
    • 右键点击系统任务栏的音量图标
    • 选择"打开音量合成器"
  1. 配置应用程序输出
    • 在音量合成器中找到你的浏览器应用
    • 点击下拉菜单,将输出设备从"扬声器"改为"Virtual Cable"
  1. 验证配置
    • 播放音频时,声音应该不会从扬声器发出(因为被重定向了)
    • 你可以在声音设置中看到虚拟音频线设备有音频活动

4、创建Python采集脚本

我们的Python脚本使用PyAudio库从默认音频输入设备(虚拟音频线)读取数据

python 复制代码
import pyaudio
import numpy as np
import wave
from datetime import datetime
import time
import signal
import sys

class PCMRecorder:
    def __init__(self, sample_rate=44100, channels=2, chunk_size=1024):
        self.sample_rate = sample_rate
        self.channels = channels
        self.chunk_size = chunk_size
        self.audio_format = pyaudio.paInt16
        self.p = pyaudio.PyAudio()
        self.is_recording = False
        self.recording_start_time = None
        
    def record_until_interrupt(self, output_file=None, device_index=None):
        """
        持续录制音频直到用户按下 Ctrl+C
        
        参数:
            output_file: 输出文件路径(可选)
            device_index: 设备索引,None使用默认设备
        """
        self.is_recording = True
        self.recording_start_time = time.time()
        
        # 设置信号处理,捕获 Ctrl+C
        original_sigint = signal.getsignal(signal.SIGINT)
        signal.signal(signal.SIGINT, self._signal_handler)
        
        # 打开音频流
        stream = self.p.open(
            format=self.audio_format,
            channels=self.channels,
            rate=self.sample_rate,
            input=True,
            input_device_index=device_index,
            frames_per_buffer=self.chunk_size
        )
        
        print("开始录制... 按 Ctrl+C 结束录制")
        print("正在录制...")
        
        frames = []
        chunk_count = 0
        last_display_time = time.time()
        
        try:
            while self.is_recording:
                # 读取音频数据
                data = stream.read(self.chunk_size, exception_on_overflow=False)
                frames.append(data)
                chunk_count += 1
                
                # 每1秒显示一次录制时长
                current_time = time.time()
                if current_time - last_display_time >= 1:
                    elapsed_time = current_time - self.recording_start_time
                    print(f"\r已录制: {self._format_time(elapsed_time)}", end="", flush=True)
                    last_display_time = current_time
                    
        except KeyboardInterrupt:
            print("\n检测到中断信号...")
        finally:
            # 恢复原来的信号处理
            signal.signal(signal.SIGINT, original_sigint)
            
            # 停止并关闭流
            stream.stop_stream()
            stream.close()
            
            # 合并所有音频数据
            if frames:
                audio_data = b''.join(frames)
                recording_duration = time.time() - self.recording_start_time
                
                print(f"\n录制结束!")
                print(f"总录制时长: {self._format_time(recording_duration)}")
                print(f"总采样点数: {len(audio_data) // 2}")  # 每个采样点2字节
                
                # 保存文件
                if output_file:
                    # 如果没有指定输出文件,生成带时间戳的文件名
                    if not output_file:
                        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                        output_file = f"recording_{timestamp}.pcm"
                    
                    # 保存原始PCM
                    with open(output_file, 'wb') as f:
                        f.write(audio_data)
                    print(f"PCM数据已保存到: {output_file}")
                    
                    # 同时保存WAV文件
                    wav_file = output_file.replace('.pcm', '.wav')
                    if wav_file == output_file:  # 如果没有.pcm扩展名
                        wav_file = output_file + '.wav'
                    self.save_wav(audio_data, wav_file)
                    print(f"WAV文件已保存到: {wav_file}")
                
                # 转换为numpy数组并返回
                np_audio = np.frombuffer(audio_data, dtype=np.int16)
                if self.channels > 1:
                    np_audio = np_audio.reshape(-1, self.channels)
                    
                return np_audio, recording_duration
            else:
                print("未录制到任何音频数据")
                return None, 0
    
    def _signal_handler(self, sig, frame):
        """处理Ctrl+C信号"""
        print("\n正在停止录制...")
        self.is_recording = False
    
    def _format_time(self, seconds):
        """格式化时间显示"""
        hours = int(seconds // 3600)
        minutes = int((seconds % 3600) // 60)
        seconds = int(seconds % 60)
        
        if hours > 0:
            return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
        else:
            return f"{minutes:02d}:{seconds:02d}"
    
    def record(self, duration=10, device_index=None, output_file=None):
        """
        录制指定时长的PCM音频
        
        参数:
            duration: 录制时长(秒)
            device_index: 设备索引,None使用默认设备
            output_file: 输出文件路径
        """
        
        # 打开音频流
        stream = self.p.open(
            format=self.audio_format,
            channels=self.channels,
            rate=self.sample_rate,
            input=True,
            input_device_index=device_index,
            frames_per_buffer=self.chunk_size
        )
        
        print(f"开始录制 {duration} 秒...")
        
        # 录制音频
        frames = []
        total_chunks = int(self.sample_rate / self.chunk_size * duration)
        
        for i in range(total_chunks):
            data = stream.read(self.chunk_size, exception_on_overflow=False)
            frames.append(data)
            # 显示进度
            if i % 50 == 0:
                progress = (i + 1) / total_chunks * 100
                print(f"\r进度: {progress:.1f}%", end="")
        
        print("\n录制完成!")
        
        # 停止并关闭流
        stream.stop_stream()
        stream.close()
        
        # 合并所有音频数据
        audio_data = b''.join(frames)
        
        # 转换为numpy数组
        np_audio = np.frombuffer(audio_data, dtype=np.int16)
        
        # 如果是立体声,重新整形
        if self.channels > 1:
            np_audio = np_audio.reshape(-1, self.channels)
        
        # 保存文件
        if output_file:
            # 保存原始PCM
            with open(output_file, 'wb') as f:
                f.write(audio_data)
            print(f"PCM数据已保存到: {output_file}")
            
            # 同时保存WAV文件
            wav_file = output_file.replace('.pcm', '.wav')
            if wav_file == output_file:  # 如果没有.pcm扩展名
                wav_file = output_file + '.wav'
            self.save_wav(audio_data, wav_file)
            print(f"WAV文件已保存到: {wav_file}")
        
        return np_audio
    
    def save_wav(self, audio_data, filename):
        """保存为WAV文件"""
        wf = wave.open(filename, 'wb')
        wf.setnchannels(self.channels)
        wf.setsampwidth(self.p.get_sample_size(self.audio_format))
        wf.setframerate(self.sample_rate)
        wf.writeframes(audio_data)
        wf.close()
    
    def __del__(self):
        """清理资源"""
        self.p.terminate()

if __name__ == "__main__":
    recorder = PCMRecorder()
    
    print("音频录制程序")
    print("=" * 50)
    print("1. 按 Ctrl+C 结束录制")
    print("2. 输出文件将自动保存为 PCM 和 WAV 格式")
    print("=" * 50)
    
    # 使用新的持续录制功能
    output_file = f"recording_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pcm"
    
    try:
        audio_data, duration = recorder.record_until_interrupt(
            output_file=output_file,
            device_index=None
        )
        
        if audio_data is not None:
            # 显示信息
            print(f"\n录制信息:")
            print(f"  总采样点数: {len(audio_data)}")
            print(f"  声道数: {recorder.channels}")
            print(f"  采样率: {recorder.sample_rate} Hz")
            print(f"  时长: {duration:.2f} 秒")
            print(f"  文件大小: {len(audio_data) * 2} 字节")
            
    except Exception as e:
        print(f"录制过程中发生错误: {e}")
脚本使用说明:
  1. 安装依赖:首先需要安装PyAudio库

    bash 复制代码
    pip install pyaudio numpy wave
  2. 运行脚本

    bash 复制代码
    python audio_recorder.py
  3. 开始录制

    • 脚本启动后会自动开始录制
    • 确保浏览器音频正在播放
    • 控制台会显示录制时长
  4. 停止录制

    • Ctrl+C 停止录制
    • 脚本会自动保存两个文件:
      • .pcm 文件:原始音频数据,适合进一步处理
      • .wav 文件:标准音频格式,可以直接播放
  5. 文件命名

    文件会自动以时间戳命名,如 recording_20251205_143000.wav

相关推荐
programer_332 小时前
本地手动创建一个MCP(windows环境)
windows·python·ai·mcp·cherry studio
电饭叔3 小时前
不含Luhn算法《python语言程序设计》2018版--第8章14题利用字符串输入作为一个信用卡号之二(识别卡号有效)
java·python·算法
观音山保我别报错3 小时前
列表,元组,字典
开发语言·python
小付爱coding3 小时前
Claude Code安装教程【windows版本】
java·git·python
高频交易dragon4 小时前
5分钟和30分钟联立进行缠论信号分析
开发语言·python
0思必得04 小时前
[Web自动化] 开发者工具应用(Application)面板
运维·前端·python·自动化·web自动化·开发者工具
我是华为OD~HR~栗栗呀4 小时前
华为OD-C面经-23届学院哦
java·c++·python·华为od·华为·面试
xiaozi41204 小时前
Ruey S. Tsay《时间序列分析》Python实现笔记:综合与应用
开发语言·笔记·python·机器学习
Aspect of twilight4 小时前
PyTorch DDP分布式训练Pytorch代码讲解
人工智能·pytorch·python