完成直播系统的第一步是采集本地音视频数据。采集本地数据要使用到FFmpeg的libavdevice 类库,我们可以利用它来采集设备数据。
使用libavdevice 需要包含其头文件,并对其注册。
#include "libavdevice/avdevice.h"
............
............
avdevice_register_all();
采集摄像头和麦克风数据与获取本地文件的方法类似,多了一步查找输入设备av_find_input_format(),参数“dshow”是指Windows上的DirectShow输入设备。“gdigrab”是基于WIN32 GDI屏幕捕获设备,允许在Windows上捕获显示区域,我们获取屏幕信息的时候会用到。"HP Wide Vision HD Camera“是我的摄像头名称
pFormatCtx = avformat_alloc_context();
//查找设备
ifmt=av_find_input_format("dshow");
avformat_open_input(&pFormatCtx,"video=HP Wide Vision HD Camera",ifmt,NULL) ;
设备名称的获取方法:
//音频设备
QList<QAudioDeviceInfo> listAudio = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
foreach(QAudioDeviceInfo audio, listAudio)
{
qDebug()<<audio.deviceName();
}
//摄像头
foreach (const QCameraInfo &cameraInfo, QCameraInfo::availableCameras()) {
qDebug()<<cameraInfo.description();
}
输出结果:
采集摄像头步骤:
1. 初始化FFmpeg,注册libavdevice。
2. 找到并打开输入设备
3. 获取输入流(avformat_find_stream_info)
4. 查找解码器,打开解码器
5. 进入循环,读取packet数据,解码。
这和之前打开本地文件的顺序是一致的,不同的是第二步,由本地文件换为了本地设备。如果要获取屏幕,麦克风数据,需要修改第二步参数。
另外,在抓取摄像头和桌面数据时都用到了图片格式转换的函数,主要的函数有三个:
(1) sws_getContext():使用参数初始化SwsContext结构体。
(2) sws_scale():转换一帧图像。
(3) sws_freeContext():释放SwsContext结构体。
代码中是将抓取的数据,以图片的格式保存到了本地,用到了QImage,需要注意QImage的储存格式要和sws_scale转换成的格式保持一致(格式RGB32一致),否则会出现异常中断。
运行结果:
完整代码:
#include <stdio.h>
#include <QImage>
#include <QString>
#include <QDebug>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/pixfmt.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
}
//捕获摄像头画面
void catchCamera()
{
AVFormatContext *pFormatCtx;//包含了封装格式的数据。
AVCodecContext *pCodecCtx;//码流数据的解码方式相关数据。
AVCodec *pCodec;//AVCodecContext中所包含的解码器信息。
AVFrame *pFrame, *pFrameRGB;//存储压缩编码数据相关信息的结构体。
AVPacket *packet;//解码后的数据
uint8_t *out_buffer;
AVInputFormat *ifmt;
//转换图片格式上下文,用于转换图片格式
static struct SwsContext *img_convert_ctx;
int videoStream, i, numBytes;
int ret, got_picture;
av_register_all(); //初始化FFMPEG 调用了这个才能正常适用编码器和解码器
//注册设备
avdevice_register_all();
//分配内存
pFormatCtx = avformat_alloc_context();
//打开摄像头
ifmt=av_find_input_format("dshow");
avformat_open_input(&pFormatCtx,"video=HP Wide Vision HD Camera",ifmt,NULL) ;
//获取视频信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
printf("Could't find stream infomation.\n");
return;
}
videoStream = -1;
///循环查找视频中包含的流信息,直到找到视频类型的流
///便将其记录下来 保存到videoStream变量中
///这里我们现在只处理视频流 音频流先不管他
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
}
}
///如果videoStream为-1 说明没有找到视频流
if (videoStream == -1) {
printf("Didn't find a video stream.\n");
return;
}
///查找解码器
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
printf("Codec not found.\n");
return;
}
///打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.\n");
return;
}
else
{
printf("打开解码器\n");
}
//分配内存
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
//初始化上下文
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
//分配缓存空间
numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
//前面的av_frame_alloc函数,只是为这个AVFrame结构体分配了内存,
//这里把av_malloc得到的内存和AVFrame关联起来。
//当然,其还会设置AVFrame的其他成员
avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
pCodecCtx->width, pCodecCtx->height);
int y_size = pCodecCtx->width * pCodecCtx->height;
packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据
int index = 0;
printf("cccccc\n");
///我们就取10张图像
for(int i=0;i<10;i++)
{
if(av_read_frame(pFormatCtx, packet) < 0)
{
printf("no frame\n");
//break;
}
if(packet->stream_index==videoStream)
{
//解码
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if(got_picture)
{
//将图片格式转换为RGB32格式,转化后的数据存在pFrameRGB中
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);
//构造函数,传入RGB数据,用QImage加载
QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
tmpImg.save(QString::number(i)+".png", "PNG", 100);
printf("catch %d\n",i);
}
}
av_free_packet(packet);
}
av_free(out_buffer);
av_free(pFrameRGB);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
}
//捕获桌面画面
void catchDesktop()
{
AVFormatContext *pFormatCtx;//包含了封装格式的数据。
AVCodecContext *pCodecCtx;//码流数据的解码方式相关数据。
AVCodec *pCodec;//AVCodecContext中所包含的解码器信息。
AVFrame *pFrame, *pFrameRGB;//存储压缩编码数据相关信息的结构体。
AVPacket *packet;//解码后的数据
uint8_t *out_buffer;
AVInputFormat *ifmt;
AVDictionary* options = NULL;
static struct SwsContext *img_convert_ctx;
int videoStream, i, numBytes;
int ret, got_picture;
av_register_all(); //初始化FFMPEG 调用了这个才能正常适用编码器和解码器
avdevice_register_all();
printf("aaaaaa\n");
//截屏
pFormatCtx = avformat_alloc_context();
//Set some options
//grabbing frame rate
av_dict_set(&options,"framerate","5",0);
//The distance from the left edge of the screen or desktop
av_dict_set(&options,"offset_x","20",0);
//The distance from the top edge of the screen or desktop
av_dict_set(&options,"offset_y","40",0);
//Video frame size. The default is to capture the full screen
// av_dict_set(&options,"video_size","640x480",0);
ifmt=av_find_input_format("gdigrab");
if(avformat_open_input(&pFormatCtx,"desktop",ifmt,&options)!=0){
printf("Couldn't open input stream.");
return;
}
//获取视频信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
printf("Could't find stream infomation.\n");
return;
}
videoStream = -1;
///循环查找视频中包含的流信息,直到找到视频类型的流
///便将其记录下来 保存到videoStream变量中
///这里我们现在只处理视频流 音频流先不管他
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
}
}
///如果videoStream为-1 说明没有找到视频流
if (videoStream == -1) {
printf("Didn't find a video stream.\n");
return;
}
///查找解码器
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
printf("Codec not found.\n");
return;
}
///打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.\n");
return;
}
else
{
printf("打开解码器\n");
}
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
pCodecCtx->width, pCodecCtx->height);
int y_size = pCodecCtx->width * pCodecCtx->height;
packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据
int index = 0;
///我们就读取10张图像
for(int i=0;i<10;i++)
{
if(av_read_frame(pFormatCtx, packet) < 0)
{
printf("no frame\n");
//break;
}
if(packet->stream_index==videoStream)
{
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if(got_picture)
{
//图像格式转换
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);
qDebug()<<pFrameRGB;
//把这个RGB数据 用QImage加载
QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,
QImage::Format_RGB32);
tmpImg.save(QString::number(i)+".png","PNG",100);
printf("catch %d\n",i);
}
}
av_free_packet(packet);
}
sws_freeContext(img_convert_ctx);
av_free(out_buffer);
av_free(pFrameRGB);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
}
//获取麦克风音频
void getVoice()
{
AVFormatContext *aFormatCtx;//包含了封装格式的数据。
AVCodecContext *aCodecCtx;//码流数据的解码方式相关数据。
AVCodec *aCodec;//AVCodecContext中所包含的解码器信息。
AVFrame *aFrame;//存储压缩编码数据相关信息的结构体。
AVPacket *packet;//解码后的数据
uint8_t *out_buffer;
AVInputFormat *ifmt;
int audioStream, i;
int ret, got_frame;
av_register_all(); //初始化FFMPEG 调用了这个才能正常适用编码器和解码器
avdevice_register_all();//初始化设备
aFormatCtx = avformat_alloc_context();
//打开麦克风,这里用到音频输入设备的设备名称
QString audioDeviceName = QStringLiteral("audio=麦克风阵列 (Realtek(R) Audio)");
ifmt = av_find_input_format("dshow");
if(avformat_open_input(&aFormatCtx,audioDeviceName.toUtf8().data(),ifmt,NULL)!=0){
//fprintf(stderr,"Couldn't open input stream.(无法打开输入流)");
printf("Couldn't open input stream.\n");
return;
}
else
{
printf("open input stream OK.\n");
}
audioStream = -1;
///循环查找视频中包含的流信息,直到找到视频类型的流
///便将其记录下来 保存到audioStream变量中
///这里我们现在只处理视频流 音频流先不管他
for (i = 0; i < aFormatCtx->nb_streams; i++) {
if (aFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {//一定注意这个地方不要和视频流的类型弄混了
audioStream = i;
}
}
///如果audioStream为-1 说明没有找到音频流
if (audioStream == -1) {
printf("Didn't find a video stream.\n");
return;
}
else
{
printf("find a video stream.\n");
}
///查找解码器
aCodecCtx = aFormatCtx->streams[audioStream]->codec;
aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
if (aCodec == NULL) {
printf("Codec not found.\n");
return;
}
else
{
printf("Codec is found.\n");
}
///打开解码器
if (avcodec_open2(aCodecCtx, aCodec, NULL) < 0) {
printf("Could not open codec.\n");
return;
}
else
{
printf("打开解码器\n");
}
aFrame = av_frame_alloc();
packet=(AVPacket *)av_malloc(sizeof(AVPacket));
FILE *fp_pcm=fopen("output.pcm","wb");
///这里打印出音频的信息
qDebug()<<"audio info:";
qDebug()<<"audio info:"<<aCodecCtx->sample_fmt<<aCodecCtx->bit_rate<<aCodecCtx->sample_rate<<aCodecCtx->channels;
float Time = 0;
for(int i=0;;i++)
{
if (Time > 10) break; //就采集10秒
if(av_read_frame(aFormatCtx, packet) < 0)
{
break;
}
if(packet->stream_index==audioStream)
{
//解码
ret = avcodec_decode_audio4(aCodecCtx, aFrame, &got_frame, packet);
if(ret < 0)
{
fprintf(stderr,"Audio Error.");
return;
}
//转换数据,写入文件
if (got_frame)
{
int pcmSize = av_samples_get_buffer_size(NULL,aCodecCtx->channels, aFrame->nb_samples,aCodecCtx->sample_fmt, 1);
uint8_t * pcmBuffer = aFrame->data[0];
//采样数/采样率=时间
float useTime = aFrame->nb_samples * 1.0 / aCodecCtx->sample_rate;
Time += useTime;
qDebug()<<i<<Time<<useTime;
fwrite(pcmBuffer,1,pcmSize,fp_pcm); //写入文件
}
}
av_free_packet(packet);
}
printf("END\n");
fclose(fp_pcm);
av_free(out_buffer);
avcodec_close(aCodecCtx);
avformat_close_input(&aFormatCtx);
}
int main()
{
// catchCamera();
catchDesktop();
// getVoice();
return 0;
}
{{ cmt.username }}
{{ cmt.content }}
{{ cmt.commentDate | formatDate('YYYY.MM.DD hh:mm') }}