之前的一直在操作USB摄像头的视频数据,如今需要读取USB摄像头的音频数据,进行音视频的合成。读取音频数据需要Linux层的ALSA驱动支持,应用层可以采用alsa-lib库,也可以采用tinyalsa库。我这里用的摄像头是罗技C920。
命令操作USB音频设备文件
可以通过alsa-lib库编译出的工具arecord
查看USB的音频设备文件,命令:arecord -l
card 2: C920 [HD Pro Webcam C920], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0
命令录制音频为WAV文件:arecord -D hw:2,0 -d 10 -f cd -r 32000 -c 2 -t wav test.wav
,命令输出如下:
Recording WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 32000 Hz, Stereo
既然提到wav文件,这里就不得不写一下wav文件的格式,以备后面查阅
wav文件格式解析
wav文件格式如下图所示:
wav文件分为RIFF区、format区、data区,下面进行详细说明
RIFF区
名称 | 偏移量 | 字节数 | 存储格式 | 内容 |
---|---|---|---|---|
ChunkID | 0x00 | 0x04 | 大端 | RIFF (0x52494646) |
ChunkSize | 0x04 | 0x04 | 小端 | 文件总长度 - 8,不含ChunkID与ChunkSize的字节数 |
Format | 0x08 | 0x04 | 大端 | ‘WAVE’(0x57415645),文件格式名称 |
- wav文件的开始均以’RIFF’为标识
- ChunkSize:是整个文件的长度减去ChunkID和ChunkSize本身的长度
- format:用‘WAVE’4个字符表示为wav格式的音频文件
fromat区
名称 | 偏移量 | 字节数 | 存储格式 | 内容 |
---|---|---|---|---|
ChunkID | 0x00 | 0x04 | 大端 | fmt (0x666D7420) |
ChunkSize | 0x04 | 0x04 | 小端 | 0x10 |
AudioFormat | 0x08 | 0x02 | 小端 | 数据格式,1表示PCM数据格式 |
NumChannel | 0x0A | 0x02 | 小端 | 音频通道数量 |
SampleRate | 0x0C | 0x04 | 小端 | 音频采样率 |
ByteRate | 0x10 | 0x04 | 小端 | 每秒钟音频数据字节数 |
BlockAlign | 0x14 | 0x02 | 小端 | 每个采样数据块字节数 |
BitsPerSample | 0x16 | 0x02 | 小端 | 每个采样量化数据的位数 |
- ChunkID:fromat区以
fmt
字符为标识 - ChunkSize:表示该区块数据的长度,固定为16字节
- AudioFormat:表示Data区块存储的音频数据的格式,PCM格式值为1
- NumChannels:表示音频数据的声道数,1:单声道,2:双声道
- SampleRate:表示音频数据的采样率
- ByteRate:每秒数据字节数 = SampleRate * NumChannels * BitsPerSample / 8
- BlockAlign:每个采样所需的字节数 = NumChannels * BitsPerSample / 8
- BitsPerSample:每个采样存储的bit数,8:8bit,16:16bit,32:32bit,一般采用16位采样
常见音频数据格式:
data区
名称 | 偏移量 | 字节数 | 存储格式 | 内容 |
---|---|---|---|---|
ChunkID | 0x00 | 0x04 | 大端 | data (0x64617461) |
ChunkSize | 0x04 | 0x04 | 小端 | 音频数据字节数 |
data | 0x08 | 0x04 | 小端 | 实际的音频数据 |
- wav文件的开始均以’RIFF’为标识
- ChunkSize:是整个文件的长度减去ChunkID和ChunkSize本身的长度
- format:用‘WAVE’4个字符表示为wav格式的音频文件
从以上可以看出,wav格式的文件起始部分占用44字节,经过以上命令保存的wav格式文件示例如下:
52 49 46 46 24 88 13 00 57 41 56 45 66 6D 74 20 10 00 00 00 01 00 02 00 00 7D 00 00 00 F4 01 00 04 00 10 00 64 61 74 61 00 88 13 00
gstreamer 合成音频为MP3文件
gst-launch-1.0 alsasrc device="hw:2,0" ! audioconvert ! imxmp3enc ! filesink location=mic.mp3
查看MP3文件信息如下:
这里遇到一个问题,当将USB的H264帧与音频进行合成时,最后视频文件在播放时视频会瞬间播放完成而且会花屏,而音频则数据播放与文件播放时长一致,这个问题还有待查明。
命令如下:
gst-launch-1.0 -e avimux name=mux1 ! filesink location=test.avi \ v4l2src device=/dev/video0 ! video/x-h264, framerate=30/1, width=640, height=360 ! mux1. \ alsasrc device=hw:2,0 ! audio/x-raw, rate=32000, channels=2, layout=interleaved, format=S16LE ! mux1.
用代码读取USB音频文件
使用代码有两种方式:
- 使用alsa-lib库,
- 使用tinyalsa-lib库
由于tinyalsa-lib库比较轻量,所以我使用这个库,其仅包含源文件limits.c
、mixer.c
、pcm.c
;三个源代码文件以及头文件,因此可以直接将其添加到自己的工程文件中。
在读取USB设备的音频之前,必须先找出其对应的设备文件,USB摄像头会生成2种设备文件,分别是video类与Audio类。
如下代码首先定义一个USB摄像头的数据结构,以下定义的数据结构为我自己的工程应用中的简化版
#define USB_VIDEO_BUF_REQ_CNT 16
typedef struct camera_node
{
struct list_head node;
char id[16];
int ch;
//V4L2
char devname[16];
int fd;
struct v4l2_format fmt;
struct v4l2_streamparm parm;
struct v4l2_requestbuffers req;
struct buffer *buffers;
int n_buffers;
//ALSA
int card;
int device;
unsigned int pcm_bytes_per_frame;
struct pcm_config config;
struct pcm *pcm;
int thread_return_value;
unsigned char ch_online;
unsigned char thread_online;
unsigned char fps;
unsigned char has_audio;
unsigned int width;
unsigned int height;
unsigned int bitrate;
unsigned int audio_buf_head;
unsigned char audio_buf[AUDIO_BUF_SIZE];
}camera_struct;
程序需要在/sys/class/video4linux
目录查找视频设备的文件的信息,在/sys/class/sound
目录查找音频设备文件的信息,代码如下:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
#include <pthread.h>
#include <time.h>
#include <sys/sem.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <dirent.h>
#include <sys/poll.h>
#include <assert.h>
#