如何录制浏览器播放的音频?虚拟音频线与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

相关推荐
SiYuanFeng7 分钟前
Colab复现 NanoChat:从 Tokenizer(CPU)、Base Train(CPU) 到 SFT(GPU) 的完整踩坑实录
python·colab
炸炸鱼.1 小时前
Python 操作 MySQL 数据库
android·数据库·python·adb
_深海凉_2 小时前
LeetCode热题100-颜色分类
python·算法·leetcode
AC赳赳老秦2 小时前
OpenClaw email技能:批量发送邮件、自动回复,高效处理工作邮件
运维·人工智能·python·django·自动化·deepseek·openclaw
zhaoshuzhaoshu2 小时前
Python 语法之数据结构详细解析
python
AI问答工程师3 小时前
Meta Muse Spark 的"思维压缩"到底是什么?我用 Python 复现了核心思路(附代码)
人工智能·python
zfan5204 小时前
python对Excel数据处理(1)
python·excel·pandas
小饕4 小时前
我从零搭建 RAG 学到的 10 件事
python
老歌老听老掉牙4 小时前
PyQt5+Qt Designer实战:可视化设计智能参数配置界面,告别手动布局时代!
python·qt
格鸰爱童话4 小时前
向AI学习项目技能(六)
java·人工智能·spring boot·python·学习