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

相关推荐
喵手2 分钟前
Python爬虫实战:地图 POI + 行政区反查实战 - 商圈热力数据准备完整方案(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·地区poi·行政区反查·商圈热力数据采集
熊猫_豆豆8 分钟前
YOLOP车道检测
人工智能·python·算法
nimadan129 分钟前
**热门短剧小说扫榜工具2025推荐,精准捕捉爆款趋势与流量
人工智能·python
默默前行的虫虫13 分钟前
MQTT.fx实际操作
python
YMWM_23 分钟前
python3继承使用
开发语言·python
JMchen12324 分钟前
AI编程与软件工程的学科融合:构建新一代智能驱动开发方法学
驱动开发·python·软件工程·ai编程
亓才孓1 小时前
[Class类的应用]反射的理解
开发语言·python
小镇敲码人1 小时前
深入剖析华为CANN框架下的Ops-CV仓库:从入门到实战指南
c++·python·华为·cann
晚霞的不甘1 小时前
CANN 编译器深度解析:TBE 自定义算子开发实战
人工智能·架构·开源·音视频
愚公搬代码1 小时前
【愚公系列】《AI短视频创作一本通》016-AI短视频的生成(AI短视频运镜方法)
人工智能·音视频