Linux下C语言实现ffmpeg 视频+音频拉流
1.环境需求
①ffmpeg源码编译
https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu官网编译教程
一定要支持h264
②alsa支持
2.源码
#include <alsa/asoundlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <pthread.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <errno.h>
#include <unistd.h>
#include <math.h>
#define CHANNELS 2
#define FSIZE 2*CHANNELS
void main()
{
//初始化网络
avformat_network_init();
//初始化设备
avdevice_register_all();
//服务器地址
char *in_name="rtmp://127.0.0.1/live/stream";
AVFormatContext* infmt_ctx=NULL;
//创建输入封装器
ret=avformat_open_input(&infmt_ctx, in_name, NULL, NULL);
if (ret != 0)
{
printf("failed alloc output context\n");
return ;
}
//读取一部分视音频流并且获得一些相关的信息
avformat_find_stream_info(infmt_ctx, NULL);
videoindex=-1,audioindex=-1,audiostop=0;
//查找视频||音频流
for(int i=0; i<infmt_ctx->nb_streams; i++)
{
if(infmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
{
videoindex=i;
}
else if(infmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)
{
audioindex=i;
}
}
printf("videoindex:%d,audioindex:%d\n");
fflush(stdout);
if (videoindex == -1)
{
printf("input video stream not exist\n");
return ;
}
if (audioindex == -1)
{
printf("input audio stream not exist\n");
return ;
}
//视频解码器
AVCodec* decodec = NULL;
//视频解码器上下文
AVCodecContext* decodec_ctx = NULL;
decodec_ctx=infmt_ctx->streams[videoindex]->codec;
//找到视频解码器
decodec = avcodec_find_decoder(decodec_ctx->codec_id);
if (!decodec)
{
printf("not find decoder\n");
avformat_close_input(&infmt_ctx);
return ;
}
//打开视频解码器
ret = avcodec_open2(decodec_ctx, decodec, NULL);
if (ret < 0) {
printf("Could not open codec:\n");
return ;
}
//音频解码器
AVCodec* decodec2 = NULL;
//音频解码器上下文
AVCodecContext* decodec_ctx2 = NULL;
decodec_ctx2=infmt_ctx->streams[audioindex]->codec;
//找到音频解码器
decodec2 = avcodec_find_decoder(decodec_ctx2->codec_id);
if (!decodec2)
{
printf("not find decoder\n");
avformat_close_input(&infmt_ctx);
return ;
}
//打开音频解码器
ret = avcodec_open2(decodec_ctx2, decodec2, NULL);
if (ret < 0) {
printf("Could not open codec:\n");
return ;
}
//查看输入封装内容
av_dump_format(infmt_ctx, 0, in_name,0);
AVFrame *pFrame,*pFrameRGB;
//视频原始帧
pFrame = av_frame_alloc();
//视频目的帧
pFrameRGB = av_frame_alloc();
pFrameRGB->format = AV_PIX_FMT_RGB24;
pFrameRGB->width = 640;
pFrameRGB->height = 480;
ret = av_frame_get_buffer(pFrameRGB, 1);
if (ret != 0)
{
printf("fail to frame get buffer\n");
}
//RGB24转换器
struct SwsContext *rgb_convert_ctx = NULL;
sws_getCachedContext(rgb_convert_ctx,
decodec_ctx->width,
decodec_ctx->height,
decodec_ctx->pix_fmt,
640,
480,
AV_PIX_FMT_RGB24,
SWS_BICUBIC, 0, 0, 0);
if (!rgb_convert_ctx)
{
printf("fail to sws_getCachedContext\n");
}
//音频AAC->PCM转换器
struct SwrContext *pcm_convert_ctx = swr_alloc();
if (!pcm_convert_ctx)
{
fprintf(stderr, "Could not allocate resampler context\n");
return ;
}
swr_alloc_set_opts(pcm_convert_ctx,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_S16,
44100,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_FLTP,
44100,
0,
NULL);
if ((ret = swr_init(pcm_convert_ctx)) < 0)
{
fprintf(stderr, "Failed to initialize the resampling context\n");
return ;
}
//音视频流包裹
AVPacket *dec_pkt;
dec_pkt = (AVPacket *)av_malloc(sizeof(AVPacket));
int got_picture=0;
int rc,err,dir;
unsigned int val;
snd_pcm_t *handle;
//以播放模式打开设备
rc = snd_pcm_open(&handle, "default",SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0)
{
fprintf(stderr,"unable to open pcm device: %s\n",snd_strerror(rc));
exit(1);
}
//配置硬件参数结构体
snd_pcm_hw_params_t *params;
//params申请内存
snd_pcm_hw_params_malloc(¶ms);
//使用pcm设备初始化hwparams
err=snd_pcm_hw_params_any(handle, params);
if (err < 0)
{
fprintf(stderr, "Can not configure this PCM device: %s\n",snd_strerror(err));
exit(1);
}
//设置多路数据在buffer中的存储方式
//SND_PCM_ACCESS_RW_INTERLEAVED每个周期(period)左右声道的数据交叉存放
err=snd_pcm_hw_params_set_access(handle, params,SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0)
{
fprintf(stderr,"Failed to set PCM device to interleaved: %s\n",snd_strerror(err));
exit(1);
}
//设置16位采样格式,S16为有符号16位,LE是小端模式
err=snd_pcm_hw_params_set_format(handle, params,SND_PCM_FORMAT_S16_LE);
if (err < 0)
{
fprintf(stderr,"Failed to set PCM device to 16-bit signed PCM: %s\n",snd_strerror(err));
exit(1);
}
//设置声道数,双声道
err=snd_pcm_hw_params_set_channels(handle, params, CHANNELS);
if (err < 0)
{
fprintf(stderr, "Failed to set PCM device to mono: %s\n",snd_strerror(err));
exit(1);
}
//采样率44100
val = 44100;
//设置采样率,如果采样率不支持,会用硬件支持最接近的采样率
err=snd_pcm_hw_params_set_rate_near(handle, params,&val, &dir);
if (err < 0)
{
fprintf(stderr, "Failed to set PCM device to sample rate =%d: %s\n",val,snd_strerror(err));
exit(1);
}
unsigned int buffer_time,period_time;
//获取最大的缓冲时间,buffer_time单位为us,500000us=0.5s
snd_pcm_hw_params_get_buffer_time_max(params, &buffer_time, 0);
if ( buffer_time >500000)
buffer_time = 500000;
//设置最大缓冲时间
err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, 0);
if (err < 0)
{
fprintf(stderr, "Failed to set PCM device to buffer time =%d: %s\n",buffer_time,snd_strerror(err));
exit(1);
}
//采样时间,出现underrun可以加大
period_time = 50000;
//设置周期时间
err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, 0);
if (err < 0)
{
fprintf(stderr, "Failed to set PCM device to period time =%d: %s\n",period_time,snd_strerror(err));
exit(1);
}
//让这些参数作用于PCM设备
rc = snd_pcm_hw_params(handle, params);
if (rc < 0)
{
fprintf(stderr,"unable to set hw parameters: %s\n",snd_strerror(rc));
exit(1);
}
//采样点数
snd_pcm_uframes_t frames;
snd_pcm_hw_params_get_period_size(params, &frames,&dir);
// 1 frame = channels * sample_size.
char *buffer;
int size = frames * FSIZE; /* 2 bytes/sample, 1 channels */
buffer = (char *) malloc(size);
//音频PCM帧
AVFrame *pframePCM;
pframePCM = av_frame_alloc();
pframePCM->format = AV_SAMPLE_FMT_S16;
pframePCM->channel_layout = AV_CH_LAYOUT_STEREO;
pframePCM->sample_rate = 44100;
pframePCM->nb_samples = frames;
pframePCM->channels = CHANNELS;
av_frame_get_buffer(pframePCM, 0);
//音频ACC帧
AVFrame *pframeAAC;
pframeAAC = av_frame_alloc();
int samples=0;
printf("开始拉音视频流\n");
while (1)
{
//获取包裹
ret=av_read_frame(infmt_ctx,dec_pkt);
if (ret != 0)
{
printf("fail to read_frame\n");
break;
}
//判断视频包裹还是音频包裹
if(dec_pkt->stream_index==videoindex)
{
//解包获取视频数据
ret = avcodec_decode_video2(decodec_ctx, pFrame, &got_picture, dec_pkt);
if(!got_picture)
{
printf("没有视频数据\n");
continue;
}
//rgb24格式转换
ret=sws_scale(rgb_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pFrameRGB->height, pFrameRGB->data, pFrameRGB->linesize);
if (ret <= 0)
{
printf("rgb24格式转换失败\n");
continue;
}
//这是qt里面信号操作
//emit img_signal(pFrameRGB);
//图片操作的话需要你们自己写。
}
else if(dec_pkt->stream_index==audioindex)
{
//解包获取音频数据
ret = avcodec_decode_audio4(decodec_ctx2, pframeAAC, &got_picture, dec_pkt);
if(!got_picture)
{
printf("没有音频帧\n");
continue;
}
//AAC->PCM格式转换
samples=swr_convert(pcm_convert_ctx,pframePCM->data, pframePCM->nb_samples,(const uint8_t **)pframeAAC->data, pframeAAC->nb_samples);
if (ret <= 0)
{
printf("AAC->PCM格式转换失败\n");
continue;
}
rc = snd_pcm_writei(handle, pframePCM->data[0], samples);
if (rc == -EPIPE)
{
fprintf(stderr, "underrun occurred\n");
err=snd_pcm_prepare(handle);
if(err <0)
{
fprintf(stderr, "can not recover from underrun: %s\n",snd_strerror(err));
}
}
else if (rc < 0)
{
fprintf(stderr,"error from writei: %s\n",snd_strerror(rc));
}
else if (rc != (int)samples)
{
fprintf(stderr,"short write, write %d frames\n", rc);
}
}
av_free_packet(dec_pkt);
}
avformat_close_input(&infmt_ctx);
snd_pcm_drain(handle);
snd_pcm_close(handle);
free(buffer);
printf("停止拉流\n");
}
3.拉流问题(暂时没有)