赞
踩
1.环境需求
①ffmpeg源码编译
https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu官网编译教程
一定要支持h264
②alsa支持
2.思路
①ffmpeg调用摄像头推流为主进程
②alsa录音并推流为线程
③利用信号量做互斥锁,让两个进程互斥推流。
3.源码
#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 //设置初值结构体 union semun{ int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */ }; //获取资源 int sem_p(int semid) { struct sembuf buf; int ret=-1; buf.sem_num=0; buf.sem_op=-1; buf.sem_flg=SEM_UNDO; ret=semop(semid,&buf,1); if(ret==-1) { perror("sem get fail!"); return -1; } return 0; } //释放资源 int sem_v(int semid) { struct sembuf buf; int ret=-1; buf.sem_num=0; buf.sem_op=+1; buf.sem_flg=SEM_UNDO; ret=semop(semid,&buf,1); if(ret==-1) { perror("sem get fail!"); return -1; } return 0; } void *thread_func(void *arg); //关键输出封装器 AVFormatContext* outfmt_ctx = NULL; //推流前准备标志 int video_ready=0,audio_ready=0; //互斥锁 int semid=-1; union semun sem_val; int main() { //初始化网络 avformat_network_init(); //初始化设备 avdevice_register_all(); //输入封装器 AVFormatContext* infmt_ctx = NULL; //视频输入格式 AVInputFormat* ifmt =NULL; //通过v4l2框架来获取视频输入格式 ifmt = av_find_input_format("linux4video2"); //视频输入设备 char *in_filename = "/dev/video0"; //视频输出设备 char *out_filename = "rtmp://47.101.62.167/live/stream"; //打开视频设备 if (0 > avformat_open_input(&infmt_ctx, in_filename, ifmt, NULL)) { printf("failed open input file\n"); return -1; } //读取设备信息 if (0 > avformat_find_stream_info(infmt_ctx, NULL)) { printf("failed find stream info\n"); avformat_close_input(&infmt_ctx); return -1; } //对流(Stream)的封装和抽象 AVStream *in_stream = NULL; AVStream *out_stream = NULL; //视频流和音频流的标志 int videoindex=-1; int i=0; int ret; //查找视频||音频流 for (i = 0; i < infmt_ctx->nb_streams; i++) { //Create output AVStream according to input AVStream if (infmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoindex = i; } else { break; } } if (videoindex == -1) { printf("input video stream not exist\n"); return -1; } AVCodec* encodec = NULL; AVCodec* decodec = NULL; //找到编码器 encodec = avcodec_find_encoder(AV_CODEC_ID_H264); if (!encodec) { printf("not find encoder\n"); avformat_close_input(&infmt_ctx); return -1; } //创建解码器上下文 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 -1; } //创建编码器上下文 AVCodecContext* encodec_ctx = NULL; encodec_ctx = avcodec_alloc_context3(encodec); if (!encodec_ctx) { printf("not alloc context3\n\n"); avformat_close_input(&infmt_ctx); return -1; } //打开解码器 ret = avcodec_open2(decodec_ctx, decodec, NULL); if (ret < 0) { fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret)); return -1; } //配置编码器参数 encodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; encodec_ctx->codec_id = encodec->id; encodec_ctx->bit_rate = 400000; encodec_ctx->width = 640; encodec_ctx->height = 480; encodec_ctx->time_base = (AVRational){1, 30}; //5是编多少帧就发送,可根据编码速度改变 encodec_ctx->framerate = (AVRational){30, 1}; encodec_ctx->gop_size = 15; encodec_ctx->max_b_frames = 0; encodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; //编码质量和速度 av_opt_set(encodec_ctx->priv_data, "preset", "ultrafast", 0); av_opt_set(encodec_ctx->priv_data, "tune", "zerolatency", 0); AVDictionary *opts = NULL; av_dict_set(&opts, "profile", "baseline", 0); //av_opt_set(encodec_ctx->priv_data, "crf", "18", 0); //打开编码器 ret = avcodec_open2(encodec_ctx, encodec, &opts); if (ret < 0) { fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret)); return -1; } //初始化输出封装器 ret=avformat_alloc_output_context2(&outfmt_ctx, NULL, "flv",out_filename); if (ret != 0) { printf("failed alloc output context\n"); avformat_close_input(&infmt_ctx); return -1;; } //添加视频流 out_stream = avformat_new_stream(outfmt_ctx,NULL); if (!out_stream) { printf("failed new stream\n"); avformat_close_input(&infmt_ctx); avformat_close_input(&outfmt_ctx); return -1; } out_stream->codecpar->codec_tag = 0; //复制参数 avcodec_parameters_from_context(out_stream->codecpar, encodec_ctx); //查看输出封装内容 av_dump_format(outfmt_ctx, 0, out_filename, 1); //创建音频推流线程 pthread_t tid; //线程ID int t_arg=100; //传入参数 if(pthread_create(&tid,NULL,thread_func,&t_arg)) //创建线程 { perror("Fail to create thread"); } //等待音频推流初始化 while(!audio_ready) { } //打开rtmp的网络输出IO ret=avio_open(&outfmt_ctx->pb, out_filename, AVIO_FLAG_WRITE); if (ret!=0) { printf("failed to open outfile\n"); avformat_close_input(&infmt_ctx); avformat_close_input(&outfmt_ctx); return -1; } //写入封装头 ret=avformat_write_header(outfmt_ctx, NULL); if (ret!=0) { printf("failed to write header\n"); avio_close(outfmt_ctx->pb); avformat_close_input(&infmt_ctx); avformat_close_input(&outfmt_ctx); return -1; } AVPacket *dec_pkt,enc_pkt; //包裹申请内存 dec_pkt = (AVPacket *)av_malloc(sizeof(AVPacket)); memset(&enc_pkt, 0, sizeof(enc_pkt)); //像素格式转换YU420 struct SwsContext *img_convert_ctx = NULL; img_convert_ctx = sws_getCachedContext(img_convert_ctx, decodec_ctx->width, decodec_ctx->height,decodec_ctx->pix_fmt, encodec_ctx->width, encodec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, 0, 0, 0); if (!img_convert_ctx) { printf("fail to sws_getCachedContext\n"); } AVFrame *pFrameYUV,*pFrame ; //原始帧 pFrame = av_frame_alloc(); //输出帧 pFrameYUV = av_frame_alloc(); pFrameYUV->format = AV_PIX_FMT_YUV420P; pFrameYUV->width = 640; pFrameYUV->height = 480; pFrameYUV->pts = 0; ret = av_frame_get_buffer(pFrameYUV, 1); if (ret != 0) { printf("fail to frame get buffer\n"); return -1; } //开始计时 int64_t start_time = av_gettime(); //标记 int got_picture=0,enc_got_frame=0; //每一帧编号 int vpts = 0; //创建信号量 semid=semget(1234,1,0666|IPC_CREAT); if(semid==-1) { perror("create sem fail!"); return -1; } //设置信号量初值 sem_val.val=1; ret=semctl(semid,0,SETVAL,sem_val); if(ret==-1) { perror("semctl set sem val fail!"); return -1; } video_ready=1; while(1) { //每一帧加1 pFrameYUV->pts = vpts; vpts+=1; //获取摄像头帧 ret=av_read_frame(infmt_ctx,dec_pkt); if (ret != 0) { printf("fail to read_frame\n"); break; } //解码获取初始图片 ret = avcodec_decode_video2(infmt_ctx->streams[dec_pkt->stream_index]->codec, pFrame, &got_picture, dec_pkt); if(!got_picture) { printf("123\n"); continue; } //h264格式转换 ret=sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, encodec_ctx->height, pFrameYUV->data, pFrameYUV->linesize); if (ret <= 0) { printf("123\n"); continue; } //输出帧编码 ret = avcodec_send_frame(encodec_ctx, pFrameYUV); if (ret != 0) { printf("123\n"); continue; } //打包到输出包裹 ret = avcodec_receive_packet(encodec_ctx, &enc_pkt); if (ret != 0 || enc_pkt.size > 0) { //cout << "*" << pack.size << flush; } else { continue; } //推流 enc_pkt.pts = av_rescale_q(enc_pkt.pts, encodec_ctx->time_base, out_stream->time_base); enc_pkt.dts = av_rescale_q(enc_pkt.dts, encodec_ctx->time_base, out_stream->time_base); enc_pkt.duration = av_rescale_q(enc_pkt.duration, encodec_ctx->time_base, out_stream->time_base); ret=sem_p(semid); if(ret==-1) { perror("sem_p fail!"); break; } //发送到服务器 ret = av_interleaved_write_frame(outfmt_ctx, &enc_pkt); if (ret < 0) { fprintf(stderr, "Error muxing packet\n"); break; } av_packet_unref(&enc_pkt); //释放信号量资源 ret=sem_v(semid); if(ret==-1) { perror("sem_p fail!"); break; } } avio_close(outfmt_ctx->pb); avformat_close_input(&infmt_ctx); avformat_close_input(&outfmt_ctx); return 0; } void *thread_func(void *arg) //线程函数 { int fd; int ret=0; snd_pcm_t *handle; //以录音模式打开设备 ret = snd_pcm_open(&handle, "default",SND_PCM_STREAM_CAPTURE, 0); if (ret < 0) { printf("unable to open pcm device!\n"); exit(1); } //配置硬件参数结构体 snd_pcm_hw_params_t *params; //params申请内存 snd_pcm_hw_params_malloc(¶ms); //使用pcm设备初始化hwparams ret=snd_pcm_hw_params_any(handle, params); if (ret < 0) { printf("Can not configure this PCM device!\n"); exit(1); } //设置多路数据在buffer中的存储方式 //SND_PCM_ACCESS_RW_INTERLEAVED每个周期(period)左右声道的数据交叉存放 ret=snd_pcm_hw_params_set_access(handle, params,SND_PCM_ACCESS_RW_INTERLEAVED); if (ret < 0) { printf("Failed to set PCM device to interleaved!\n"); exit(1); } //设置16位采样格式 ret=snd_pcm_hw_params_set_format(handle, params,SND_PCM_FORMAT_S16_LE); if (ret < 0) { printf("Failed to set PCM device to 16-bit signed PCM\n"); exit(1); } //设置声道数 ret=snd_pcm_hw_params_set_channels(handle, params, CHANNELS); if (ret < 0) { printf("Failed to set PCM device CHANNELS\n"); exit(1); } unsigned int val=44100; int dir; //设置采样率,如果采样率不支持,会用硬件支持最接近的采样率 ret=snd_pcm_hw_params_set_rate_near(handle, params,&val, &dir); if (ret < 0) { printf("Failed to set PCM device to sample rate\n"); 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); //printf("buffer_time:%d\n",buffer_time); if ( buffer_time >500000) buffer_time = 500000; //设置缓冲时间 ret = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, 0); if (ret < 0) { printf("Failed to set PCM device to sample rate\n"); exit(1); } //设置周期时间,设置为37帧/s,1/37=0.023219 period_time = 23219; ret = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, 0); if (ret < 0) { printf("Failed to set PCM device to period time\n"); exit(1); } //让这些参数作用于PCM设备 ret = snd_pcm_hw_params(handle, params); if (ret < 0) { printf("unable to set hw parameters\n"); exit(1); } snd_pcm_uframes_t frames; snd_pcm_hw_params_get_period_size(params,&frames, &dir); printf("period_size:%ld\n",frames); int size; // 1 frame = channels * sample_size. size = frames * FSIZE; /* 2 bytes/sample, 1 channels */ printf("size:%d\n",size); char *buffer; buffer = (char *) malloc(size); 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); AVFrame *pframeAAC; pframeAAC = av_frame_alloc(); pframeAAC->format = AV_SAMPLE_FMT_FLTP; pframeAAC->channel_layout = AV_CH_LAYOUT_STEREO; pframeAAC->sample_rate = 44100; pframeAAC->nb_samples = 1024; pframeAAC->channels = CHANNELS; av_frame_get_buffer(pframeAAC, 0); struct SwrContext *aac_convert_ctx = swr_alloc(); if (!aac_convert_ctx) { fprintf(stderr, "Could not allocate resampler context\n"); return -1; } swr_alloc_set_opts(aac_convert_ctx, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLTP, 44100, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, 44100, 0, NULL); if ((ret = swr_init(aac_convert_ctx)) < 0) { fprintf(stderr, "Failed to initialize the resampling context\n"); return -1; } AVCodec* encodec = NULL; //找到编码器 encodec = avcodec_find_encoder(AV_CODEC_ID_AAC); if (!encodec) { printf("not find encoder\n"); return -1; } AVCodecContext* encodec_ctx = NULL; //创建编码器 encodec_ctx = avcodec_alloc_context3(encodec); if (!encodec_ctx) { printf("not alloc context3\n\n"); return -1; } encodec_ctx->codec_id = encodec->id; encodec_ctx->codec_type = AVMEDIA_TYPE_AUDIO; encodec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; encodec_ctx->bit_rate = 64000; encodec_ctx->sample_rate = 44100; encodec_ctx->channel_layout = AV_CH_LAYOUT_STEREO ; encodec_ctx->channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); //打开解码器 ret = avcodec_open2(encodec_ctx, encodec, NULL); if (ret < 0) { fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret)); return -1; } //服务器地址 char *out_name="rtmp://47.101.62.167/live/stream"; AVStream *out_stream = NULL; //添加音频流 out_stream = avformat_new_stream(outfmt_ctx,NULL); if (!out_stream) { printf("failed new stream\n"); return -1; } //复制参数 avcodec_parameters_from_context(out_stream->codecpar, encodec_ctx); //查看输出封装内容 av_dump_format(outfmt_ctx, 0, out_name, 1); AVPacket enc_pkt; memset(&enc_pkt, 0, sizeof(enc_pkt)); int got_picture; int i,vpts=0; char *p; struct timeval start, end; gettimeofday( &start, NULL ); audio_ready=1; while(!video_ready) { } while (1) { ret = snd_pcm_readi(handle, buffer, frames); if (ret == -EPIPE) { // EPIPE means overrun fprintf(stderr, "overrun occurred\n"); ret=snd_pcm_prepare(handle); if(ret <0){ printf("Failed to recover form overrun"); exit(1); } } else if (ret < 0) { fprintf(stderr,"error from read: %s\n",snd_strerror(ret)); exit(1); } else if (ret != (int)frames) { fprintf(stderr, "short read, read %d frames\n", ret); } memcpy(pframePCM->data[0],buffer,size); ret=swr_convert(aac_convert_ctx,pframeAAC->data, pframeAAC->nb_samples,(const uint8_t **)pframePCM->data, pframePCM->nb_samples); avcodec_encode_audio2(encodec_ctx, &enc_pkt, pframeAAC, &got_picture); if(!got_picture) { printf("123\n"); continue; } //推流 enc_pkt.pts = av_rescale_q(enc_pkt.pts, encodec_ctx->time_base, out_stream->time_base); enc_pkt.dts = av_rescale_q(enc_pkt.dts, encodec_ctx->time_base, out_stream->time_base); enc_pkt.duration = av_rescale_q(enc_pkt.duration, encodec_ctx->time_base, out_stream->time_base); enc_pkt.stream_index=1; //获取信号量资源 ret=sem_p(semid); if(ret==-1) { perror("sem_p fail!"); break; } ret = av_interleaved_write_frame(outfmt_ctx, &enc_pkt); if (ret < 0) { fprintf(stderr, "Error muxing packet\n"); break; } av_free_packet(&enc_pkt); //释放信号量资源 ret=sem_v(semid); if(ret==-1) { perror("sem_p fail!"); break; } pframeAAC->pts = vpts; vpts+=pframeAAC->nb_samples; gettimeofday( &end, NULL ); printf("%ld",end.tv_sec-start.tv_sec); printf("\r\033[k"); fflush(stdout); } printf("audio off\n"); snd_pcm_drain(handle); snd_pcm_close(handle); free(buffer); }
4.各种问题
①音频推流突然就卡住了
②音频与视频不同步,音频慢5秒
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。