Python中使用vlc库制作播放器,如何获取音频数据并生成音量柱?

最近在使用 wxpython+python-vlc 来做一个直播轮询的软件,中间遇到个问题,有些直播是纯音频内容,那么直播是否正常就需要有音量柱来体现,目前一直卡在这个地方,segmentfault 上提问了,没人解答,想看看 V2EX 又没有大神能指点下。因为 wxpython 比较多,只贴下 vlc 播放的简单代码。
import vlc
import time
player = vlc.MediaPlayer(‘you-video-url’)
player.play()
time.sleep(20)
Python中使用vlc库制作播放器,如何获取音频数据并生成音量柱?


1 回复

要获取VLC播放器的音频数据并生成音量柱,可以用libvlc_audio_set_callbacks配合numpymatplotlib。下面是个完整示例:

import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.colors import LinearSegmentedColormap
import vlc

class AudioVisualizer:
    def __init__(self):
        # 初始化VLC实例和播放器
        self.instance = vlc.Instance('--no-xlib')
        self.player = self.instance.media_player_new()
        
        # 音频参数
        self.sample_rate = 44100
        self.channels = 2
        self.buffer_size = 1024  # 每次回调的数据量
        
        # 存储音频数据
        self.audio_data = np.zeros(self.buffer_size)
        self.lock = False
        
        # 设置音频回调
        self.player.audio_set_callbacks(self._play_cb, self._pause_cb, 
                                       self._resume_cb, self._flush_cb, 
                                       self._drain_cb)
        self.player.audio_set_format_callback(self._setup_cb, self._cleanup_cb)
        
        # 创建图形界面
        self.fig, self.ax = plt.subplots(figsize=(10, 4))
        self.fig.patch.set_facecolor('#2b2b2b')
        self.ax.set_facecolor('#1e1e1e')
        
        # 创建自定义颜色映射(从绿到红)
        colors = ['#00ff00', '#ffff00', '#ff0000']
        self.cmap = LinearSegmentedColormap.from_list('volume', colors, N=100)
        
        # 初始化柱状图
        self.bars = self.ax.bar(range(50), [0]*50, 
                               color=self.cmap(0),
                               edgecolor='white',
                               linewidth=0.5)
        
        # 美化图形
        self.ax.set_ylim(0, 1)
        self.ax.set_xlim(0, 50)
        self.ax.set_xticks([])
        self.ax.set_yticks([])
        self.ax.spines['top'].set_visible(False)
        self.ax.spines['right'].set_visible(False)
        self.ax.spines['bottom'].set_visible(False)
        self.ax.spines['left'].set_visible(False)
        
        self.ax.text(0.5, 1.05, 'Volume Visualization', 
                    transform=self.ax.transAxes,
                    ha='center', fontsize=14, color='white',
                    fontweight='bold')
    
    def _setup_cb(self, opaque, format_ptr, rate_ptr, channels_ptr):
        """设置音频格式回调"""
        format_ptr.contents.value = vlc.AudioFormat.S16N
        rate_ptr.contents.value = self.sample_rate
        channels_ptr.contents.value = self.channels
        return 0
    
    def _cleanup_cb(self, opaque):
        """清理回调"""
        return 0
    
    def _play_cb(self, opaque, samples, count, pts):
        """播放回调 - 在这里获取音频数据"""
        if self.lock or count == 0:
            return 0
        
        self.lock = True
        try:
            # 将字节数据转换为numpy数组
            audio_array = np.frombuffer(samples[:count*4], dtype=np.int16)
            
            # 转换为浮点数并归一化
            if len(audio_array) > 0:
                audio_float = audio_array.astype(np.float32) / 32768.0
                
                # 取左右声道的平均值
                if self.channels == 2:
                    audio_float = audio_float.reshape(-1, 2).mean(axis=1)
                
                # 更新音频数据(取最新数据)
                self.audio_data = audio_float[:self.buffer_size]
                if len(self.audio_data) < self.buffer_size:
                    self.audio_data = np.pad(self.audio_data, 
                                           (0, self.buffer_size - len(self.audio_data)))
        
        finally:
            self.lock = False
        
        return count
    
    def _pause_cb(self, opaque, pts):
        return 0
    
    def _resume_cb(self, opaque, pts):
        return 0
    
    def _flush_cb(self, opaque):
        return 0
    
    def _drain_cb(self, opaque):
        return 0
    
    def update_plot(self, frame):
        """更新柱状图动画"""
        if not self.lock and len(self.audio_data) > 0:
            # 计算RMS音量
            rms = np.sqrt(np.mean(self.audio_data**2))
            
            # 生成频谱数据(简化版)
            fft_data = np.abs(np.fft.rfft(self.audio_data)[:50])
            fft_normalized = fft_data / np.max(fft_data) if np.max(fft_data) > 0 else fft_data
            
            # 更新每个柱子的高度和颜色
            for i, bar in enumerate(self.bars):
                height = fft_normalized[i] if i < len(fft_normalized) else 0
                bar.set_height(height)
                
                # 根据音量和位置设置颜色
                color_value = min(rms * 2 + i/100, 1.0)
                bar.set_color(self.cmap(color_value))
                bar.set_edgecolor(self.cmap(color_value))
        
        return self.bars
    
    def play(self, media_path):
        """播放媒体文件"""
        media = self.instance.media_new(media_path)
        self.player.set_media(media)
        self.player.play()
        
        # 启动动画
        ani = animation.FuncAnimation(self.fig, self.update_plot, 
                                     interval=50, blit=True)
        plt.show()

# 使用示例
if __name__ == "__main__":
    visualizer = AudioVisualizer()
    visualizer.play("your_audio_file.mp3")  # 替换为你的音频文件路径

关键点说明:

  1. 音频回调设置:通过audio_set_callbacks注册回调函数,_play_cb是获取原始PCM数据的关键
  2. 数据处理:将16位整数转换为浮点,计算RMS音量,用FFT生成频谱数据
  3. 可视化:用matplotlib创建动画柱状图,颜色随音量变化
  4. 线程安全:用锁防止数据竞争

注意:需要先安装python-vlc, numpy, matplotlib。这个方案直接操作音频缓冲区,比用audio_get_volume获取的音量数据更精确。

简单说就是注册回调拿原始音频数据,算RMS和频谱,用matplotlib画动态柱子。

回到顶部