精华内容
下载资源
问答
  • 1,摄像头数据推流音频数据推流,接口简单,几行代码可实现直播推流,以及监控功能。 2,权限请求。 3,内有一套编译好的ffmpeg,因此库可以使用NDK开发使用。 4,集成了Camera2的使用。 5,集成GPU美颜模块...
  • ffmpeg转码推流

    2018-09-03 16:54:22
    可以安装ffmpeg,进行配置,可以使用命令行进行音视频的编解码
  • 这是windows下ffmpeg推流桌面与摄像头数据到流媒体服务器源码,该软件里推流和视频保存使用FFMPEG库完成,视频和音频可以同步推流和录制,FFMPEG本身支持跨平台编译开发,QT也支持跨平台,在Android、Linux、windows...
  • Linux下C语言实现ffmpeg视频+音频推流 1.环境需求 ①ffmpeg源码编译 https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu官网编译教程 一定要支持h264 ②alsa支持 2.思路 ①ffmpeg调用摄像头推流为主进程 ②alsa...

    Linux下C语言实现ffmpeg视频+音频推流

    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(&params);
        //使用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秒

    展开全文
  • 如果已经完成FFMPEG录制视频保存到本地的功能,完成RTMP推流只需要修改几行代码即可完成。 主要修改的代码: filename="rtmp://js.live-send.acg.tv/live-js/?xxxxxxxx" avformat_alloc_output_context2(&oc...

    如果已经完成FFMPEG录制视频保存到本地的功能,完成RTMP推流只需要修改几行代码即可完成。

    推流到RTMP服务器与保存到本地的代码基本相同,主要是输出地址不一样。保存到本地就是本地文件名称,推流到RTMP服务器,就将文件名称换成RTMP服务器地址即可。

    完整项目代码下载地址(下载即可编译运行,不懂可以私信): https://download.csdn.net/download/xiaolong1126626497/19323232

    主要修改的代码:

    filename="rtmp://js.live-send.acg.tv/live-js/?xxxxxxxx"
    avformat_alloc_output_context2(&oc,nullptr,"flv",filename); //文件名称替换成网络地址
    
    //指定编码器
    fmt->video_codec=AV_CODEC_ID_H264;
    fmt->audio_codec=AV_CODEC_ID_AAC;
    

     

    FFMPEG保存视频到本地的文章:

    https://blog.csdn.net/xiaolong1126626497/article/details/104858499

     

    video_audio_encode.cpp的完整代码:

    #include "video_audio_encode.h"
    class VideoAudioEncode videoaudioencode;
    Thread_VideoAudioEncode thread_VideoenCode; //视频音频编码的线程
    
    char audio_buffer[AUDIO_BUFFER_MAX_SIZE];     //音频缓存
    int audio_buffer_r_count=0;
    int audio_buffer_w_count=0;
    
    //音频相关参数设置
    #define AUDIO_RATE_SET 44100      //音频采样率
    #define AUDIO_BIT_RATE_SET  64000 //设置码率
    #define AUDIO_CHANNEL_SET   AV_CH_LAYOUT_MONO  //AV_CH_LAYOUT_MONO 单声道   AV_CH_LAYOUT_STEREO 立体声
    
    #define STREAM_DURATION   60*10.0   //录制时间秒单位
    #define STREAM_FRAME_RATE 30     /* 30 images/s   图片是多少帧1秒*/
    #define STREAM_PIX_FMT    AV_PIX_FMT_YUV420P /* default pix_fmt 视频图像格式 */
    #define SCALE_FLAGS SWS_BICUBIC
    
    // 单个输出AVStream的包装器
    typedef struct OutputStream {
        AVStream *st;
        AVCodecContext *enc;
    
        /* 下一帧的点数*/
        int64_t next_pts;
        int samples_count;
    
        AVFrame *frame;
        AVFrame *tmp_frame;
    
        float t, tincr, tincr2;
    
        struct SwsContext *sws_ctx;
        struct SwrContext *swr_ctx;
    } OutputStream;
    
    static int write_frame(AVFormatContext *fmt_ctx, const AVRational *time_base, AVStream *st, AVPacket *pkt)
    {
        /*将输出数据包时间戳值从编解码器重新调整为流时基 */
        av_packet_rescale_ts(pkt, *time_base, st->time_base);
        pkt->stream_index = st->index;
        /*将压缩的帧写入媒体文件*/
        return av_interleaved_write_frame(fmt_ctx, pkt);
    }
    
    /* 添加输出流。 */
    static void add_stream(OutputStream *ost, AVFormatContext *oc,
                           AVCodec **codec,
                           enum AVCodecID codec_id)
    {
        AVCodecContext *c;
        int i;
    
        /* find the encoder */
        *codec = avcodec_find_encoder(codec_id);
        if (!(*codec)) {
            qDebug("Could not find encoder for '%s'\n",avcodec_get_name(codec_id));
            exit(1);
        }
    
        ost->st = avformat_new_stream(oc, nullptr);
        if (!ost->st) {
            qDebug("Could not allocate stream\n");
            exit(1);
        }
        ost->st->id = oc->nb_streams-1;
        c = avcodec_alloc_context3(*codec);
        if (!c) {
            qDebug("Could not alloc an encoding context\n");
            exit(1);
        }
        ost->enc = c;
    
        switch ((*codec)->type) {
        case AVMEDIA_TYPE_AUDIO:
            //设置数据格式
            //c->sample_fmt  = (*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
            c->sample_fmt  = AV_SAMPLE_FMT_FLTP;
            c->bit_rate    = AUDIO_BIT_RATE_SET;  //设置码率
            c->sample_rate = AUDIO_RATE_SET;  //音频采样率
            //编码器支持的采样率
            if ((*codec)->supported_samplerates)
            {
                c->sample_rate = (*codec)->supported_samplerates[0];
                for (i = 0; (*codec)->supported_samplerates[i]; i++)
                {
                    //判断编码器是否支持
                    if ((*codec)->supported_samplerates[i] == AUDIO_RATE_SET)
                    {
                         c->sample_rate = AUDIO_RATE_SET;
                    }
                }
            }
    
            //设置采样通道
            c->channels= av_get_channel_layout_nb_channels(c->channel_layout);
            c->channel_layout = AUDIO_CHANNEL_SET; //AV_CH_LAYOUT_MONO 单声道   AV_CH_LAYOUT_STEREO 立体声
            if ((*codec)->channel_layouts)
            {
                c->channel_layout = (*codec)->channel_layouts[0];
                for (i = 0; (*codec)->channel_layouts[i]; i++)
                {
                    if ((*codec)->channel_layouts[i] == AUDIO_CHANNEL_SET)
                    {
                        c->channel_layout = AUDIO_CHANNEL_SET;
                    }
                }
            }
            c->channels        = av_get_channel_layout_nb_channels(c->channel_layout);
            ost->st->time_base = (AVRational){ 1, c->sample_rate };
            break;
    
        case AVMEDIA_TYPE_VIDEO:
            c->codec_id = codec_id;
            //码率:影响体积,与体积成正比:码率越大,体积越大;码率越小,体积越小。
            c->bit_rate = 400000; //设置码率 400kps
            /*分辨率必须是2的倍数。 */
            c->width    = 640;
            c->height   = 480;
            /*时基:这是基本的时间单位(以秒为单位)
             *表示其中的帧时间戳。 对于固定fps内容,
             *时基应为1 / framerate,时间戳增量应为
             *等于1。*/
            ost->st->time_base = (AVRational){1,STREAM_FRAME_RATE};
            c->time_base       = ost->st->time_base;
            c->gop_size      = 12; /* 最多每十二帧发射一帧内帧 */
            c->pix_fmt       = STREAM_PIX_FMT;
            if(c->codec_id == AV_CODEC_ID_MPEG2VIDEO)
            {
                /* 只是为了测试,添加了B帧 */
                c->max_b_frames = 2;
            }
            if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
                /*需要避免使用其中一些系数溢出的宏块。
                 *普通视频不会发生这种情况,因为
                 *色度平面的运动与亮度平面不匹配。 */
                c->mb_decision = 2;
            }
        break;
    
        default:
            break;
        }
    
        /* 某些格式希望流头分开。 */
        if (oc->oformat->flags & AVFMT_GLOBALHEADER)
            c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }
    
    /**************************************************************/
    /* audio output */
    
    static AVFrame *alloc_audio_frame(enum AVSampleFormat sample_fmt,
                                      uint64_t channel_layout,
                                      int sample_rate, int nb_samples)
    {
        AVFrame *frame = av_frame_alloc();
        int ret;
    
        if (!frame) {
            qDebug("Error allocating an audio frame\n");
            exit(1);
        }
    
        frame->format = sample_fmt;
        frame->channel_layout = channel_layout;
        frame->sample_rate = sample_rate;
        frame->nb_samples = nb_samples;
    
        if (nb_samples) {
            ret = av_frame_get_buffer(frame, 0);
            if (ret < 0) {
                qDebug("Error allocating an audio buffer\n");
                exit(1);
            }
        }
    
        return frame;
    }
    
    static void open_audio(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg)
    {
        AVCodecContext *c;
        int nb_samples;
        int ret;
        AVDictionary *opt = nullptr;
    
        c = ost->enc;
    
        /* open it */
        av_dict_copy(&opt, opt_arg, 0);
        ret = avcodec_open2(c, codec, &opt);
        av_dict_free(&opt);
        if (ret < 0) {
            qDebug("无法打开音频编解码器\n");
            exit(1);
        }
    
        /* 初始化信号发生器 */
        ost->t     = 0;
        ost->tincr = 2 * M_PI * 110.0 / c->sample_rate;
        /* 每秒增加110 Hz的频率 */
        ost->tincr2 = 2 * M_PI * 110.0 / c->sample_rate / c->sample_rate;
    
        if (c->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
            nb_samples = 10000;
        else
            nb_samples = c->frame_size;
    
        ost->frame     = alloc_audio_frame(c->sample_fmt, c->channel_layout,
                                           c->sample_rate, nb_samples);
        ost->tmp_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, c->channel_layout,
                                           c->sample_rate, nb_samples);
    
        /*将流参数复制到多路复用器 */
        ret = avcodec_parameters_from_context(ost->st->codecpar, c);
        if (ret < 0) {
            qDebug("无法复制流参数\n");
            exit(1);
        }
    
        /* 创建重采样器上下文 */
            ost->swr_ctx = swr_alloc();
            if(!ost->swr_ctx)
            {
                qDebug("无法分配重采样器上下文\n");
                exit(1);
            }
           /* 设定选项 */
            av_opt_set_int       (ost->swr_ctx, "in_channel_count",   c->channels,       0);
            av_opt_set_int       (ost->swr_ctx, "in_sample_rate",     c->sample_rate,    0);
            av_opt_set_sample_fmt(ost->swr_ctx, "in_sample_fmt",      AV_SAMPLE_FMT_S16, 0);//带符号16bit
            av_opt_set_int       (ost->swr_ctx, "out_channel_count",  c->channels,       0);
            av_opt_set_int       (ost->swr_ctx, "out_sample_rate",    c->sample_rate,    0);
            av_opt_set_sample_fmt(ost->swr_ctx, "out_sample_fmt",     c->sample_fmt,     0);
    
            qDebug("音频通道数=%d\n",c->channels);
            qDebug("音频采样率=%d\n",c->sample_rate);
    
            /* 初始化重采样上下文 */
            if ((ret = swr_init(ost->swr_ctx)) < 0) {
                qDebug("无法初始化重采样上下文\n");
                exit(1);
            }
    }
    
    /*准备一个'frame_size'样本的16位虚拟音频帧,然后'nb_channels'频道。 */
    static AVFrame *get_audio_frame(OutputStream *ost)
    {
        AVFrame *frame = ost->tmp_frame;
        int j, i, v;
        int16_t *q = (int16_t*)frame->data[0];
    
        /* 检查我们是否要生成更多帧----用于判断是否结束*/
        if(av_compare_ts(ost->next_pts, ost->enc->time_base,
                          STREAM_DURATION, (AVRational){ 1, 1 }) >= 0)
            return nullptr;
    
        // qDebug("frame->nb_samples=%d\n",frame->nb_samples); //1024
        // qDebug("ost->enc->channels=%d\n",ost->enc->channels);
    
        //消费者
        // videoaudioencode.audio_encode_mutex.lock();
        // videoaudioencode.audio_encode_Condition.wait(&videoaudioencode.audio_encode_mutex);
        // memcpy(audio_buffer_temp,audio_buffer,sizeof(audio_buffer));
        // videoaudioencode.audio_encode_mutex.unlock();
    
    #if 1
        if(audio_buffer_r_count>=AUDIO_BUFFER_MAX_SIZE)audio_buffer_r_count=0;
        //音频数据赋值
        for(j = 0; j<frame->nb_samples; j++)  //nb_samples: 此帧描述的音频样本数(每个通道)
        {
            for(i=0;i<ost->enc->channels;i++)  //channels:音频通道数
            {
                *q++ = audio_buffer[j+audio_buffer_r_count];  //音频数据
            }
            ost->t     += ost->tincr;
            ost->tincr += ost->tincr2;
        }
        frame->pts = ost->next_pts;
        ost->next_pts  += frame->nb_samples;
        //qDebug()<<"audio_buffer_r_count="<<audio_buffer_r_count;
        audio_buffer_r_count+=1024;
    #else
    
        for(j = 0; j<frame->nb_samples; j++)  //nb_samples: 此帧描述的音频样本数(每个通道)
           {
               v=(int)(sin(ost->t) * 1000);
               for(i=0;i<ost->enc->channels;i++)  //channels:音频通道数
               {
                   *q++ = v;  //音频数据
               }
               ost->t     += ost->tincr;
               ost->tincr += ost->tincr2;
           }
        frame->pts = ost->next_pts;
        ost->next_pts  += frame->nb_samples;
    #endif
        return frame;
    }
    
    /*
     *编码一个音频帧并将其发送到多路复用器
     *编码完成后返回1,否则返回0
     */
    static int write_audio_frame(AVFormatContext *oc, OutputStream *ost)
    {
        AVCodecContext *c;
        AVPacket pkt = { 0 }; // data and size must be 0;
        AVFrame *frame;
        int ret;
        int got_packet;
        int dst_nb_samples;
    
        av_init_packet(&pkt);
        c = ost->enc;
    
        frame = get_audio_frame(ost);
    
        if(frame)
        {
            /*使用重采样器将样本从本机格式转换为目标编解码器格式*/
             /*计算样本的目标数量*/
                dst_nb_samples = av_rescale_rnd(swr_get_delay(ost->swr_ctx, c->sample_rate) + frame->nb_samples,
                                                c->sample_rate, c->sample_rate, AV_ROUND_UP);
                av_assert0(dst_nb_samples == frame->nb_samples);
    
            /*当我们将帧传递给编码器时,它可能会保留对它的引用
             *内部;
             *确保我们不会在这里覆盖它
             */
            ret = av_frame_make_writable(ost->frame);
            if (ret < 0)
                exit(1);
    
            /*转换为目标格式 */
            ret = swr_convert(ost->swr_ctx,
                              ost->frame->data, dst_nb_samples,
                              (const uint8_t **)frame->data, frame->nb_samples);
            if (ret < 0) {
                qDebug("Error while converting\n");
                exit(1);
            }
            frame = ost->frame;
    
            frame->pts = av_rescale_q(ost->samples_count, (AVRational){1, c->sample_rate}, c->time_base);
            ost->samples_count += dst_nb_samples;
        }
    
        ret = avcodec_encode_audio2(c, &pkt, frame, &got_packet);
        if (ret < 0) {
            qDebug("Error encoding audio frame\n");
            exit(1);
        }
    
        if (got_packet) {
            ret = write_frame(oc, &c->time_base, ost->st, &pkt);
            if (ret < 0) {
                qDebug("Error while writing audio frame\n");
                exit(1);
            }
        }
    
        return (frame || got_packet) ? 0 : 1;
    }
    
    /**************************************************************/
    /* video output */
    
    static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
    {
        AVFrame *picture;
        int ret;
    
        picture = av_frame_alloc();
        if (!picture)
            return nullptr;
    
        picture->format = pix_fmt;
        picture->width  = width;
        picture->height = height;
    
        /* allocate the buffers for the frame data */
        ret = av_frame_get_buffer(picture, 32);
        if(ret < 0)
        {
            qDebug("Could not allocate frame data.\n");
            exit(1);
        }
    
        return picture;
    }
    
    static void open_video(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg)
    {
        int ret;
        AVCodecContext *c = ost->enc;
        AVDictionary *opt = nullptr;
    
        av_dict_copy(&opt, opt_arg, 0);
    
        /* open the codec */
        ret = avcodec_open2(c, codec, &opt);
        av_dict_free(&opt);
        if (ret < 0) {
            qDebug("Could not open video codec\n");
            exit(1);
        }
    
        /* allocate and init a re-usable frame */
        ost->frame = alloc_picture(c->pix_fmt, c->width, c->height);
        if (!ost->frame) {
            qDebug("Could not allocate video frame\n");
            exit(1);
        }
        ost->tmp_frame = nullptr;
        /* 将流参数复制到多路复用器 */
        ret = avcodec_parameters_from_context(ost->st->codecpar, c);
        if (ret < 0) {
            qDebug("Could not copy the stream parameters\n");
            exit(1);
        }
    }
    
    
    /*
    准备图像数据
    YUV422占用内存空间 = w * h * 2
    YUV420占用内存空间 = width*height*3/2
    */
    static int fill_yuv_image(AVFrame *pict, int frame_index,int width, int height)
    {
        unsigned int y_size=width*height;
        //消费者
        while(videoaudioencode.void_data_queue.isEmpty())
        {
            QThread::msleep(10);
            if(videoaudioencode.run_flag==0) //停止编码
            {
                return -1;
            }
        }
        videoaudioencode.video_encode_mutex.lock();
        QByteArray byte=videoaudioencode.void_data_queue.dequeue();
        //qDebug()<<"out="<<videoaudioencode.void_data_queue.size();
        videoaudioencode.video_encode_mutex.unlock();
    
        //将YUV数据拷贝到缓冲区  y_size=wXh
        memcpy(pict->data[0],byte.data(),y_size);
        memcpy(pict->data[1],byte.data()+y_size,y_size/4);
        memcpy(pict->data[2],byte.data()+y_size+y_size/4,y_size/4);
        return 0;
    }
    
    static AVFrame *get_video_frame(OutputStream *ost)
    {
        AVCodecContext *c = ost->enc;
    
        /* 检查我们是否要生成更多帧---判断是否结束录制 */
          if(av_compare_ts(ost->next_pts, c->time_base,STREAM_DURATION, (AVRational){ 1, 1 }) >= 0)
            return nullptr;
    
        /*当我们将帧传递给编码器时,它可能会保留对它的引用
        *内部; 确保我们在这里不覆盖它*/
        if (av_frame_make_writable(ost->frame) < 0)
            exit(1);
    
        //制作虚拟图像
        //DTS(解码时间戳)和PTS(显示时间戳)
        int err=fill_yuv_image(ost->frame, ost->next_pts, c->width, c->height);
        if(err)return nullptr;
        ost->frame->pts = ost->next_pts++;
        return ost->frame;
    }
    
    
    /*
    *编码一个视频帧并将其发送到多路复用器
    *编码完成后返回1,否则返回0
    */
    static int write_video_frame(AVFormatContext *oc, OutputStream *ost)
    {
        int ret;
        AVCodecContext *c;
        AVFrame *frame;
        int got_packet = 0;
        AVPacket pkt = {0};
        c=ost->enc;
        //获取一帧数据
        frame = get_video_frame(ost);
        if(frame==nullptr)return 1;
    
        av_init_packet(&pkt);
    
        /* 编码图像 */
        ret=avcodec_encode_video2(c, &pkt, frame, &got_packet);
        if(ret < 0)
        {
            qDebug("Error encoding video frame\n");
            exit(1);
        }
    
        if(got_packet)
        {
            ret=write_frame(oc, &c->time_base, ost->st, &pkt);
        }
        else
        {
            ret = 0;
        }
    
        if(ret < 0)
        {
            qDebug("Error while writing video frame\n");
            exit(1);
        }
        return (frame || got_packet) ? 0 : 1;
    }
    
    
    static void close_stream(AVFormatContext *oc, OutputStream *ost)
    {
        avcodec_free_context(&ost->enc);
        av_frame_free(&ost->frame);
        av_frame_free(&ost->tmp_frame);
        sws_freeContext(ost->sws_ctx);
        swr_free(&ost->swr_ctx);
    }
    
    int Thread_VideoAudioEncode::StartUp_VideoAudioEncode()
    {
        OutputStream video_st = {0}, audio_st = { 0 };
        AVOutputFormat *fmt;
        AVFormatContext *oc;
        AVCodec *audio_codec, *video_codec;
        int ret;
        int have_video = 0, have_audio = 0;
        int encode_video = 0, encode_audio = 0;
        AVDictionary *opt = nullptr;
    
        QDateTime dateTime(QDateTime::currentDateTime());
        //时间效果: 2020-03-05 16:25::04 周四
        QString qStr="";
        qStr+=SAVE_FILE_PATH;  //Android 手机的照相机文件夹
        qStr+=dateTime.toString("yyyy-MM-dd-hh-mm-ss");
        qStr+=".mp4";
        qStr="rtmp://js.live-send.acg.tv/live-js/?streamname=live_68130189_71037877&key=b95d4cfda0c196518f104839fe5e7573";
        char filename[500];
        strcpy(filename,qStr.toLatin1().data());
        emit LogSend(tr("当前的文件名称:%1\n").arg(filename));
    
       /* 分配输出环境 */
        //avformat_alloc_output_context2(&oc,nullptr,nullptr,filename); //存放到文件
        avformat_alloc_output_context2(&oc,nullptr,"flv",filename); //发布到网络
        if(!oc)
        {
            emit LogSend("code error.\n");
            return -1;
        }
    
        fmt=oc->oformat;
        fmt->video_codec=AV_CODEC_ID_H264;
        fmt->audio_codec=AV_CODEC_ID_AAC;
         /*使用默认格式的编解码器添加音频和视频流,初始化编解码器。 */
        if(fmt->video_codec != AV_CODEC_ID_NONE)
        {
            add_stream(&video_st,oc,&video_codec,fmt->video_codec);
            have_video = 1;
            encode_video = 1;
        }
        if(fmt->audio_codec != AV_CODEC_ID_NONE)
        {
            add_stream(&audio_st, oc, &audio_codec, fmt->audio_codec);
            have_audio = 1;
            encode_audio = 1;
        }
    
      /*现在已经设置了所有参数,可以打开音频视频编解码器,并分配必要的编码缓冲区。 */
        if (have_video)
            open_video(oc, video_codec, &video_st, opt);
    
        if (have_audio)
            open_audio(oc, audio_codec, &audio_st, opt);
    
        av_dump_format(oc, 0, filename, 1);
    
        /* 打开输出文件(如果需要) */
        if(!(fmt->flags & AVFMT_NOFILE))
        {
            qDebug()<<"存放视频到文件.\n";
            ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
            if (ret < 0)
            {
                qDebug("Could not open '%s'\n",filename);
                return -1;
            }
        }
    
        /* 编写流头(如果有)*/
        ret=avformat_write_header(oc,&opt);
        if(ret<0)
        {
            qDebug("Error occurred when opening output file\n");
            return -1;
        }
    
        while(encode_video || encode_audio)
        {
            /* 选择要编码的流*/
            if(encode_video &&(!encode_audio || av_compare_ts(video_st.next_pts, video_st.enc->time_base,audio_st.next_pts, audio_st.enc->time_base) <= 0))
            {
                QElapsedTimer mstimer;
                mstimer.start();
    
                encode_video = !write_video_frame(oc,&video_st);
    
                double time = (double)mstimer.nsecsElapsed()/(double)1000000;
                qDebug() <<"video_encode:"<< time<<"ms";
            }
            else
            {
                QElapsedTimer mstimer;
                mstimer.start();
    
                encode_audio = !write_audio_frame(oc,&audio_st);
    
                double time = (double)mstimer.nsecsElapsed()/(double)1000000;
                qDebug() <<"audio_encode:"<< time<<"ms";
            }
        }
    
        /*编写预告片(如果有)。 预告片必须在之前写好
        *关闭在编写标头时打开的CodecContext; 除此以外
        * av_write_trailer()可能会尝试使用已释放的内存
        * av_codec_close()。 */
    
        av_write_trailer(oc);
    
        /* Close each codec. */
        if (have_video)
            close_stream(oc, &video_st);
        if (have_audio)
            close_stream(oc, &audio_st);
    
        if (!(fmt->flags & AVFMT_NOFILE))
            /* Close the output file. */
            avio_closep(&oc->pb);
    
        /* free the stream */
        avformat_free_context(oc);
        qDebug("编码完成.线程退出.\n");
        return 0;
    }
    
    //编码
    void Thread_VideoAudioEncode ::run()
    {
        while(1)
        {
            qDebug()<<"编码线程开始运行.";
            audio_buffer_r_count=0;
            audio_buffer_w_count=0;
            StartUp_VideoAudioEncode(); //启动视频音频编码
            if(videoaudioencode.run_flag==0) //判断是否停止编码
            {
                break;
            }
        }
    }
    

     

    展开全文
  • 音视频开发---ffmpeg rtmp推流

    万次阅读 2019-07-31 18:01:48
    FFmpeg推流 推流器函数流程图 代码 遗留问题 参考 推流介绍 推流是将输入视频数据推送至流媒体服务器, 输入视频数据可以是本地视频文件(avi,mp4,flv......),也可以是内存视频数据,或者摄像头等系统设备...

    目录

    推流介绍

    FFmpeg推流

    推流器函数流程图

    代码

    遗留问题

    参考


     

    推流介绍

    推流是将输入视频数据推送至流媒体服务器, 输入视频数据可以是本地视频文件(avi,mp4,flv......),也可以是内存视频数据,或者摄像头等系统设备,也可以是网络流URL。本篇介绍将本地视频文件通过FFmpeg编程以RTMP直播流的形式推送至RTMP流媒体服务器的方法。

    推流的网络拓扑结构如下:

    RTMP流媒体服务器: 这里采用nginx+rtmp module来实现,可以参考nginx+rtmp服务器搭建

    RTMP拉流器:参考:

    RTMP推流器:采用ffmpeg实现

    需要注意的是,RTMP采用的封装格式是FLV。在指定输出流媒体格式的时候需要指定其封装格式为“flv”。同理,其他流媒体协议也需要指定其封装格式。例如采用UDP推送流媒体的时候,可以指定其封装格式为“mpegts”。

     

    FFmpeg推流

    FFMpeg处理RTMP流有两种方式:
      一个是使用自带的RTMP代码功能;
      一个是使用第三方库librtmp(RTMPDump);

    这两种方式是有些区别的

    1. FFmpeg自带的RTMP代码功能
      FFmpeg自带的RTMP代码只支持RTMP协议,不支持rtmpt,rtmpe,rtmpte和rtmps协议;
    命令行设置如下:
    1. 将RTMP流原样保存成文件
    # ./ffmpeg -i rtmp://192.168.1.11:1935/live/teststream -acodec copy -vcodec copy -f flv -y test.flv


    2. 将RTMP流转码保存成文件
    # ./ffmpeg -i rtmp://192.168.1.11:1935/live/teststream -acodec ... -vcodec ... -f mp4 -y test.mp4


    3. 将RTMP流转码后再以RTMP流的方式推送到RTMP流服务器
    # ./ffmpeg -i rtmp://192.168.1.11:1935/live/teststream -acodec ... -vcodec ... -f flv rtmp://10.2.11.111/live/newstream


    2. 第三方库librtmp
    如何让FFMpeg链接该库可以参见文章:
    http://blog.csdn.net/fireroll/article/details/8607955

    这样FFMpeg就可以支持rtmp://, rtmpt://, rtmpe://, rtmpte://,以及 rtmps://协议了。
    链接了librtmp的FFMpeg接受一个字符串的输入方式,
    如:"rtmp://server:port/app/playpath/stream_name live=1 playpath=xxx ..."
    NOTE:引号是必须的;

    1. 保存RTMP直播流原样保存成文件:
    # ./ffmpeg -i "rtmp://pub1.guoshi.com/live/newcetv1 live=1" -vcodec copy -acodec copy -y cetv1.flv   

    2. 将RTMP流转码后再以RTMP流的方式推送到RTMP流服务器
    # ./ffmpeg -i "rtmp://192.168.1.11:1935/live/app/teststream live=1" -acodec ... -vcodec ... -f flv rtmp://10.2.11.111/live/newstream

    3. 用ffplay播放RTMP直播流:
    ffplay "rtmp://pub1.guoshi.com/live/newcetv1 live=1" 

    4. 在使用FFMPEG类库进行编程的时候,也是一样的,
    只需要将字符串传递给avformat_open_input()就行了,形如:
    ffplay "rtmp://pub1.guoshi.com/live/newcetv1 live=1"  

    char url[]="rtmp://live.hkstv.hk.lxdns.com/live/hks live=1";  
    avformat_open_input(&pFormatCtx,url,NULL,&avdic)  

     

    推流器函数流程图

     


     

     

    代码

     

    int main(int argc, char * argv[])
    {    
    
        AVFormatContext *pInFmtContext = NULL;    
        AVStream *in_stream;
        
        AVCodecContext *pInCodecCtx;
        AVCodec *pInCodec;
        AVPacket *in_packet;
    
    
        AVFormatContext * pOutFmtContext;
        AVOutputFormat *outputFmt;
        AVStream * out_stream;
        //AVCodecContext * pOutCodecCtx;
        //AVCodec *pOutCodec;
        //AVPacket *out_packet; 
        //AVFrame *pOutFrame;
        AVRational frame_rate; 
        double duration;
        
    	//int picture_size = 0;
        //FILE *fp; 
        int ret;
        const char * default_url = "rtmp://localhost:1935/live/tuiliu1";
        char in_file[128] = {0};
        char out_file[256] = {0};
        
        int videoindex = -1;
    	int audioindex = -1;
    	int video_frame_count = 0;
    	int audio_frame_count = 0;
    	int video_frame_size = 0;
    	int audio_frame_size = 0;
        int i;
        int got_picture;
    
    
        if(argc < 2){
            printf("Usage: a.out <in_filename> <url>\n");
            return -1;
        }
        
        memcpy(in_file, argv[1], strlen(argv[1]));
        if( argc == 2){
            memcpy(out_file, default_url, strlen(default_url));
        }else{
            memcpy(out_file, argv[2], strlen(argv[2]));
        }
    
        //av_register_all();
        //avformat_network_init();
    
    
        // Open an input stream and read the header, 
        if (avformat_open_input ( &pInFmtContext, in_file, NULL, NULL) < 0){
            printf("avformat_open_input failed\n");         
    		return -1;    
        }
        
        //查询输入流中的所有流信息
    	if( avformat_find_stream_info(pInFmtContext, NULL) < 0){
    		printf("avformat_find_stream_info failed\n");
    		return -1;
    	}
        //print 
    	av_dump_format(pInFmtContext, 0, in_file, 0); 
    
    
        ret = avformat_alloc_output_context2(&pOutFmtContext, NULL, "flv", out_file);
        if(ret < 0){
            printf("avformat_alloc_output_context2 failed\n");
            return -1;
        }        
        //outputFmt = pOutFmtContext->oformat;
    
    
        for(i=0; i < pInFmtContext->nb_streams; i++){
            in_stream = pInFmtContext->streams[i];
    		if( in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
    			audioindex = i;
    		}
            if( in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
                videoindex = i;
                frame_rate = av_guess_frame_rate(pInFmtContext, in_stream, NULL);
                printf("video: frame_rate:%d/%d\n", frame_rate.num, frame_rate.den);
                
                printf("video: frame_rate:%d/%d\n", frame_rate.den, frame_rate.num);
                duration = av_q2d((AVRational){frame_rate.den, frame_rate.num});       
            }
            
            pInCodec = avcodec_find_decoder(in_stream->codecpar->codec_id);
            printf("%x, %d\n", pInCodec, in_stream->codecpar->codec_id);
            //printf("-----%s,%s\n", pInCodec->name, in_stream->codec->codec->name);
            out_stream = avformat_new_stream(pOutFmtContext,  pInCodec);//in_stream->codec->codec);
            if( out_stream == NULL){
                printf("avformat_new_stream failed:%d\n",i);
            }
    
            ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
            if( ret < 0){
                printf("avcodec_parameters_copy failed:%d\n", i);
            }
    
            out_stream->codecpar->codec_tag = 0;
    
            if( pOutFmtContext->oformat->flags & AVFMT_GLOBALHEADER){//AVFMT_GLOBALHEADER代表封装格式包含“全局头”(即整个文件的文件头),大部分封装格式是这样的。一些封装格式没有“全局头”,比如MPEG2TS
                out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            }        
        }
    
    
    	av_dump_format(pOutFmtContext, 0, out_file, 1); 
    
        ret = avio_open(&pOutFmtContext->pb, out_file, AVIO_FLAG_WRITE);
        if(ret < 0){
            printf("avio_open failed:%d\n", ret);
            return -1;
        }
        int64_t start_time = av_gettime();
    
        ret = avformat_write_header(pOutFmtContext, NULL);
    
        in_packet = av_packet_alloc();
        while(1){
            ret = av_read_frame(pInFmtContext, in_packet);
            if(ret < 0){
                printf("read frame end\n");
                break;
            }
            in_stream = pInFmtContext->streams[in_packet->stream_index];
    		
    		if(in_packet->stream_index == videoindex){
    			video_frame_size += in_packet->size;
                printf("recv %5d video frame %5d-%5d\n", ++video_frame_count, in_packet->size, video_frame_size);
            }
    
            
            if(in_packet->stream_index == audioindex){
    			audio_frame_size += in_packet->size;
                printf("recv %5d audio frame %5d-%5d\n", ++audio_frame_count, in_packet->size, audio_frame_size);
            }
    		
            int codec_type = in_stream->codecpar->codec_type;
            if( codec_type == AVMEDIA_TYPE_VIDEO){
    #if 0     
         //延时方案1:  根据 1/帧率 来计算延时时间
                av_usleep((int64_t)(duration * AV_TIME_BASE));
                //av_usleep(10);
            printf("%d\n", (int)(duration * AV_TIME_BASE));
    #else
        // 延时方案2: 根据pts时间与系统时间的关系来计算延时时间,   该方案更优
            AVRational  dst_time_base = {1, AV_TIME_BASE};
            int64_t pts_time = av_rescale_q(in_packet->pts, in_stream->time_base, dst_time_base); 
            int64_t now_time = av_gettime() - start_time;
            if( pts_time > now_time)
                av_usleep(pts_time - now_time);
            //printf("%d\n", pts_time - now_time);
    #endif
            
            }
    
            out_stream = pOutFmtContext->streams[in_packet->stream_index];
            av_packet_rescale_ts(in_packet,in_stream->time_base, out_stream->time_base);
            in_packet->pos = -1;
            ret = av_interleaved_write_frame(pOutFmtContext, in_packet);
            if( ret < 0){
                printf("av_interleaved_write_frame failed\n");
    
                break;
            }
            av_packet_unref(in_packet);
            
        }
    
    //
        av_write_trailer(pOutFmtContext);
        av_packet_free(&in_packet);
    
        avformat_close_input(&pInFmtContext);
        avio_close( pOutFmtContext->pb);
        avformat_free_context(pOutFmtContext);
    	return 0;
    }

     有两点需要注意的地方

    1. 推流的速度

          不能一下子将数据全推到服务器,这样流媒体服务器承受不住,实际中音频流的数据量相比视频要小很多,可以不必管它, 只按视频播放速度(帧率)来推流即可满足需要。因此每推送一个视频帧,要延时一个视频帧的时长。视频帧的时长可根据帧率计算得出,即 1/帧率。

          上述代码中采用的是av_usleep()直接延时等待的方式, , 等待时间为‘1/帧率’, 由于存在程序处理的时间,系统延时等, 这种方式控制时间是不准确的,但是上述代码却很直观的表现了推流延时的实现。 实际中,我们需要考虑系统执行的时间以及延时, 可以结合系统当前时间与视频帧pts时间之间的差距,来决定延时的时间,这样计算的延时时间相对更准确, 计算公式如下:

    delay_time = pts_time - now_time
    
               =   av_rescale_q(pkt.dts, ifmt_ctx->streams[videoindex]->time_base, (AVRational){1,AV_TIME_BASE})  -  (av_gettime() - start_time)

    2. 推流的类型

         上述代码采用的推流协议是rtmp, rtmp推流必须推送flv封装格式,而其他的协议也有相应的格式要求(udp推流必须推送mpegts封装格式)。 如果要将上述代码修改为适配多种推流协议,则可根据推流协议自动选择相应的封装格式。

    编译

    gcc tuiliu1.c  -lavformat -lavcodec -lavutil
    

    验证

          要验证推流程序是否正确,我们需要搭建一个nginx+rtmp流媒体服务器(搭建nginx+rtmp服务器),而拉流端可以使用ffplay,参考以下过程:

    1. 启动nginx服务器
        nginx 
    
    2. 启动拉流
        ffplay rtmp://localhost:1935/live/tuiliu1
    
    
    3. 启动推流:
       ./a.out test.flv
    
    
    接下来,就能看视频了

     

    遗留问题

    1.  无论使用ffplay命令播放视频还是使用SDL编程播放视频,都会导致compiz占用cpu过高

        也尝试过网上的多种解决方式,均无法解决,怀疑是compiz的bug

    2. 无论使用ffmpeg命令推流还是使用以上代码推流,都会在推流结束调用av_write_trailer时打印

    [flv @ 0x858c440] Failed to update header with correct duration.
    [flv @ 0x858c440] Failed to update header with correct filesize.

    使用以下推流命令依然会打印以上信息。

            ffmpeg -re -i test.mp4 -c copy -f flv rtmp://localhost:1935/live/tuiliu1

     

    参考

    雷霄骅 RTMP流媒体技术零基础学习方法

    https://cloud.tencent.com/developer/article/1415557

    https://blog.csdn.net/leixiaohua1020/article/details/39803457

    https://www.cnblogs.com/leisure_chn

    http://blog.chinaunix.net/uid-26000296-id-4095806.html

     

    展开全文
  • ffmpeg usb摄像头采集 +6818硬件编码 +ffmpeg RTMP H264推流 需要提前移植QT 和FFMPEG ffmpeg移植不需要添加H264编码
  • 自己花了点时间实现了一个使用FFmpeg将Camera2视频数据送到RTMP服务的简单Demo,在这里分享下,里面用到知识很多都是之前博客中用到的,难度不大。1、 定义方法定义了三个JNI方法public class FFmpegHandler {...

    自己花了点时间实现了一个使用FFmpeg将Camera2视频数据推送到RTMP服务的简单Demo,在这里分享下,里面用到知识很多都是之前博客中用到的,难度不大。

    1、 定义方法

    定义了三个JNI方法

    public class FFmpegHandler {

    private FFmpegHandler() {

    }

    private static class SingletonInstance {

    private static final FFmpegHandler INSTANCE = new FFmpegHandler();

    }

    public static FFmpegHandler getInstance() {

    return SingletonInstance.INSTANCE;

    }

    static {

    System.loadLibrary("ffmpeg-handler");

    }

    //初始化参数

    public native int init(String outUrl);

    //推流,将Y、U、V数据分开传递

    public native int pushCameraData(byte[] buffer,int ylen,byte[] ubuffer,int ulen,byte[] vbuffer,int vlen);

    //结束

    public native int close();

    }

    2、Camera2实时数据

    将ImageReader作为预览请求的Target之一,这样我们就可以将预览的数据拿到在onImageAvailable中进行处理推送。

    mImageReader = ImageReader.newInstance(640, 480,ImageFormat.YUV_420_888, 1);

    mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);

    Surface imageSurface = mImageReader.getSurface();

    mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

    mPreviewRequestBuilder.addTarget(surface);

    mPreviewRequestBuilder.addTarget(imageSurface);

    将获取的Image数据解析为YUV数据,Y、U、V数据分别存储。具体请看YUV数据格式与YUV_420_888。

    目前这块暂时这样写着,网上的博客都比较旧了,有点不太合适,我想应该还会有更好的方法,后面再做优化。(或者这块你有什么好的处理方法,欢迎留言)。

    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener

    = new ImageReader.OnImageAvailableListener() {

    @Override

    public void onImageAvailable(ImageReader reader) {

    Image image = reader.acquireLatestImage();

    if (image == null) {

    return;

    }

    final Image.Plane[] planes = image.getPlanes();

    int width = image.getWidth();

    int height = image.getHeight();

    // Y、U、V数据

    byte[] yBytes = new byte[width * height];

    byte uBytes[] = new byte[width * height / 4];

    byte vBytes[] = new byte[width * height / 4];

    //目标数组的装填到的位置

    int dstIndex = 0;

    int uIndex = 0;

    int vIndex = 0;

    int pixelsStride, rowStride;

    for (int i = 0; i < planes.length; i++) {

    pixelsStride = planes[i].getPixelStride();

    rowStride = planes[i].getRowStride();

    ByteBuffer buffer = planes[i].getBuffer();

    //如果pixelsStride==2,一般的Y的buffer长度=640*480,UV的长度=640*480/2-1

    //源数据的索引,y的数据是byte中连续的,u的数据是v向左移以为生成的,两者都是偶数位为有效数据

    byte[] bytes = new byte[buffer.capacity()];

    buffer.get(bytes);

    int srcIndex = 0;

    if (i == 0) {

    //直接取出来所有Y的有效区域,也可以存储成一个临时的bytes,到下一步再copy

    for (int j = 0; j < height; j++) {

    System.arraycopy(bytes, srcIndex, yBytes, dstIndex, width);

    srcIndex += rowStride;

    dstIndex += width;

    }

    } else if (i == 1) {

    //根据pixelsStride取相应的数据

    for (int j = 0; j < height / 2; j++) {

    for (int k = 0; k < width / 2; k++) {

    uBytes[uIndex++] = bytes[srcIndex];

    srcIndex += pixelsStride;

    }

    if (pixelsStride == 2) {

    srcIndex += rowStride - width;

    } else if (pixelsStride == 1) {

    srcIndex += rowStride - width / 2;

    }

    }

    } else if (i == 2) {

    //根据pixelsStride取相应的数据

    for (int j = 0; j < height / 2; j++) {

    for (int k = 0; k < width / 2; k++) {

    vBytes[vIndex++] = bytes[srcIndex];

    srcIndex += pixelsStride;

    }

    if (pixelsStride == 2) {

    srcIndex += rowStride - width;

    } else if (pixelsStride == 1) {

    srcIndex += rowStride - width / 2;

    }

    }

    }

    }

    // 将YUV数据交给C层去处理。

    FFmpegHandler.getInstance().pushCameraData(yBytes, yBytes.length, uBytes, uBytes.length, vBytes, vBytes.length);

    image.close();

    }

    };

    3、初始化FFmpeg

    直播推送的过程整体就是一个先将视频数据编码,再将编码后的数据写入数据流中推送给服务器的过程。

    下面初始化的过程就是准备好数据编码器和一条已经连上服务器的数据流

    JNIEXPORT jint JNICALL Java_com_david_camerapush_ffmpeg_FFmpegHandler_init

    (JNIEnv *jniEnv, jobject instance, jstring url) {

    const char *out_url = (*jniEnv)->GetStringUTFChars(jniEnv, url, 0);

    //计算yuv数据的长度

    yuv_width = width;

    yuv_height = height;

    y_length = width * height;

    uv_length = width * height / 4;

    //output initialize

    int ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_url);

    if (ret < 0) {

    LOGE("avformat_alloc_output_context2 error");

    }

    //初始化H264编码器

    pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);

    if (!pCodec) {

    LOGE("Can not find encoder!\n");

    return -1;

    }

    pCodecCtx = avcodec_alloc_context3(pCodec);

    //编码器的ID号,这里为264编码器

    pCodecCtx->codec_id = pCodec->id;

    //像素的格式,也就是说采用什么样的色彩空间来表明一个像素点,这里使用YUV420P

    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

    //编码器编码的数据类型

    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;

    //编码目标的视频帧大小,以像素为单位

    pCodecCtx->width = width;

    pCodecCtx->height = height;

    //帧频

    pCodecCtx->framerate = (AVRational) {15, 1};

    //时间基

    pCodecCtx->time_base = (AVRational) {1, 15};

    //目标的码率,即采样的码率;显然,采样码率越大,视频大小越大

    pCodecCtx->bit_rate = 400000;

    pCodecCtx->gop_size = 50;

    /* Some formats want stream headers to be separate. */

    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)

    pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    //H264 codec param

    pCodecCtx->qcompress = 0.6;

    //最大和最小量化系数

    pCodecCtx->qmin = 10;

    pCodecCtx->qmax = 51;

    //Optional Param

    //两个非B帧之间允许出现多少个B帧数

    //设置0表示不使用B帧,b 帧越多,图片越小

    pCodecCtx->max_b_frames = 0;

    AVDictionary *param = 0;

    //H.264

    if (pCodecCtx->codec_id == AV_CODEC_ID_H264) {

    av_dict_set(&param, "preset", "superfast", 0); //x264编码速度的选项

    av_dict_set(&param, "tune", "zerolatency", 0);

    }

    // 打开编码器

    if (avcodec_open2(pCodecCtx, pCodec, &param) < 0) {

    LOGE("Failed to open encoder!\n");

    return -1;

    }

    // 新建传输流,即将要直播的视频流

    video_st = avformat_new_stream(ofmt_ctx, pCodec);

    if (video_st == NULL) {

    return -1;

    }

    video_st->time_base = (AVRational) {25, 1};

    video_st->codecpar->codec_tag = 0;

    avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);

    // 打开数据流,表示与rtmp服务器连接

    int err = avio_open(&ofmt_ctx->pb, out_url, AVIO_FLAG_READ_WRITE);

    if (err < 0) {

    LOGE("Failed to open output:%s", av_err2str(err));

    return -1;

    }

    //Write File Header

    avformat_write_header(ofmt_ctx, NULL);

    av_init_packet(&enc_pkt);

    return 0;

    }

    4、开始传输

    对YUV数据编码,并将编码后数据写入准备好的直播流中。

    JNIEXPORT jint JNICALL Java_com_david_camerapush_ffmpeg_FFmpegHandler_pushCameraData

    (JNIEnv *jniEnv, jobject instance, jbyteArray yArray, jint yLen, jbyteArray uArray, jint uLen, jbyteArray vArray, jint vLen) {

    jbyte *yin = (*jniEnv)->GetByteArrayElements(jniEnv, yArray, NULL);

    jbyte *uin = (*jniEnv)->GetByteArrayElements(jniEnv, uArray, NULL);

    jbyte *vin = (*jniEnv)->GetByteArrayElements(jniEnv, vArray, NULL);

    int ret = 0;

    // 初始化Frame

    pFrameYUV = av_frame_alloc();

    int picture_size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width,

    pCodecCtx->height, 1);

    uint8_t *buffers = (uint8_t *) av_malloc(picture_size);

    //将buffers的地址赋给AVFrame中的图像数据,根据像素格式判断有几个数据指针

    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, buffers, pCodecCtx->pix_fmt,pCodecCtx->width, pCodecCtx->height, 1);

    // Frame中数据填充

    memcpy(pFrameYUV->data[0], yin, (size_t) yLen); //Y

    memcpy(pFrameYUV->data[1], uin, (size_t) uLen); //U

    memcpy(pFrameYUV->data[2], vin, (size_t) vLen); //V

    pFrameYUV->pts = count;

    pFrameYUV->format = AV_PIX_FMT_YUV420P;

    pFrameYUV->width = yuv_width;

    pFrameYUV->height = yuv_height;

    //初始化AVPacket

    enc_pkt.data = NULL;

    enc_pkt.size = 0;

    //开始编码YUV数据

    ret = avcodec_send_frame(pCodecCtx, pFrameYUV);

    if (ret != 0) {

    LOGE("avcodec_send_frame error");

    return -1;

    }

    //获取编码后的H264数据

    ret = avcodec_receive_packet(pCodecCtx, &enc_pkt);

    if (ret != 0 || enc_pkt.size <= 0) {

    LOGE("avcodec_receive_packet error %s", av_err2str(ret));

    return -2;

    }

    enc_pkt.stream_index = video_st->index;

    enc_pkt.pts = count * (video_st->time_base.den) / ((video_st->time_base.num) * fps);

    enc_pkt.dts = enc_pkt.pts;

    enc_pkt.duration = (video_st->time_base.den) / ((video_st->time_base.num) * fps);

    enc_pkt.pos = -1;

    // 往直播流写数据

    ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);

    if (ret != 0) {

    LOGE("av_interleaved_write_frame failed");

    }

    count++;

    //释放内存,Java写多了经常会忘记这块**

    av_packet_unref(&enc_pkt);

    av_frame_free(&pFrameYUV);

    av_free(buffers);

    (*jniEnv)->ReleaseByteArrayElements(jniEnv, yArray, yin, 0);

    (*jniEnv)->ReleaseByteArrayElements(jniEnv, uArray, uin, 0);

    (*jniEnv)->ReleaseByteArrayElements(jniEnv, vArray, vin, 0);

    return 0;

    }

    效果

    bd960a320e74

    image

    这是Demo运行后的结果,推送视频OK,但是可能会有2到3秒的延迟(可能也跟网速有关)。目前就做到这种程度,后面会优化延迟、音频直播、音视频同步等都会慢慢加上去。

    Tips:

    图片中使用的在Windows下的nginx-rtmp-win32,不需要编译,点击exe就可以运行。

    展开全文
  • 麦克风喊话,实时的传来MP3音频buf,需要实时的将这个音频buf传到rtmp服务器
  • ffmpeg命令:rtp音频推流拉流

    千次阅读 2020-12-24 11:10:40
    音频输出 # pcm ffmpeg -re -f s16le -ac 1 -ar 8000 -i .\test_8000.pcm -f rtp rtp://192.168.1.101:29000 # pcm 转码 g711a ffmpeg -re -f s16le -ac 1 -ar 8000 -i .\test_8000.pcm -c:a pcm_alaw -f alaw...
  • 最近在做一个基于RTMP推流的项目,将实现的代码整理了供大家参考,推流环境使用ffmpeg进行推流,nginx做流媒体服务器,VLC进行拉流。
  • FFmpeg4Android:视频文件推流到nginx服务器(源码) RTMP推流器(Streamer)的在流媒体系统中的作用可以用下图表示。首先将视频数据以RTMP的形式发送到流媒体服务器端(Server,比如FMS,Red5,Wowza等),然后...
  •      FFmpeg+Qt,实现录制本地的麦克风音频后进行UDP的组播推流。并由推流组播地址进行拉流,拉流后进行音频的播放。在推流部分增加了可选噪声控制,于是在拉流端播放后可听到噪音干扰。   预览 视频...
  • 这是基于QT+FFMPEG推流客户端。 QT版本5.12.6. 编译器: MinGW 32 支持: 1. 推流摄像头图像+音频到流媒体服务器 2. 推流桌面图像+音频到流媒体服务器 3. 录制摄像头图像+音频到本地 4. 录制桌面图像+音频到本地 ...
  • 本地搭建直播流媒体服务 LiveQing直播点播流媒体服务下载 利用ffmpeg查看采集设备 ffmpeg -hide_banner -list_...音频采集推流 ffmpeg -f dshow -i audio="麦克风阵列 (Realtek High Definition Audio)" -codec:a...
  • 压缩包里包含了在windows下可用运行的基于FFMPEG设计的视频推流与录制软件。 并包含了在win32(X86)系统 下可以使用的FFMPEG动态库和头文件,可以直接拿去在windows下做音频视频开发使用。
  • 研究了雷博的博客,视频单独推流没问题,音频单独推流也没得问题。但是同步的时候问题来了。也不知道自己错在哪里 新手,没有C币 只能厚颜向大家提问了。 ``` while (1) { //Wait SDL_WaitEvent(&event);...
  • ffmpeg实现实时录音并推流的功能

    千次阅读 2017-10-13 16:26:54
    基本方法就是在原有的推流代码基础上(比如rtmp推流的代码),修改打开输入设备的代码改为如下类似的代码,调整一下源文件的变量等设置,就可以实现了: //输入(Input) // if ((ret = avformat_open_input(&ifmt_...
  • RTSP, RTMP协议推流, 以及保存到本地MP4,MKV录像文件。 如果要成功编译, 需要下载和编译ffmpeg库,libfdk-aac库,x264库。 编译这些库,非常耗时。如果懒得去编译, 可以直接使用已经编译好的stream_push.dll...
  • ffmpeg rtmp推流代码示例

    千次阅读 2020-01-31 21:22:11
    rtmp推流,得有rtmp服务器,可以参考这篇博客搭建rtmp服务器 ===》》》ubuntu搭建rtmp服务器,如果自己有rtmp服务器,可以不用看。 rtmp推流需要用flv格式, 本篇博客demo是本地flv文件rtmp推流. 主要流程如下: ...
  • ffmpeg -fflags nobuffer -f dshow -i audio="麦克风 (2- Realtek High Definition Audio)" -acodec aac -f flv rtmp://localhost:1935/live/test1
  • ffmpeg推流之后,隔几秒卡顿一下

    千次阅读 2020-01-13 17:01:02
    实测是ffmpeg推流的时候,fps参数设置的不对,当fps设置的过大时,过一段时间,画面就会卡顿一下,因为推流端没有视频流了。fps参数设置太小时,画面也会卡顿(具体原因,有待深究)。 ...
  • 由于使用obs推流音频始终有各种各样的问题,所以目前打算直接使用FFmpeg推送音频流,期间遇到了各种坑,特此记录。 视频+音频 首先安装好FFmpeg,然后可以用以下命令测试,我们转发服务器用的是janus,也可以用...
  • 今天看到一篇文章,用树莓派 + ffmpeg推流电视剧到 B 站,突然也想到我可以用树莓派做一个音乐站,加上电子相册什么的。于是折腾了一天的 ffmpeg录屏并推流直接用这个命令即可:ffmpeg -f pulse -i alsa_output....
  • FFmpeg 推流问题记录

    2020-12-24 07:06:02
    1)推流结束之后,没有在m3u8文件添加#EXT-X-ENDLIST,导致浏览器播放结束之后,画面中间一直转圈,表示继续加载,没有停止推流采用的是ffmpeg + flv + rtmp方式如下是各种无效方案:1avio_printf(m_oFmtCtx->...
  • FFmpeg进行推流的两种方法

    千次阅读 2021-05-30 19:38:05
    FFmpeg推流一般有两种: 1.使用cmd进行推流,这种推流方式一般可用于简单的推流验证 这里拿
  • ffmpeg C++推流

    千次阅读 2018-11-09 15:59:58
    ffmpeg 推流器代码如下: #include &lt;iostream&gt; using namespace std; //引入头文件 extern "C" { #include "libavformat/avformat.h" //引入时间 #include "libavutil/...
  • ffmpeg视频推流的方法

    万次阅读 2019-07-23 14:21:44
    UDP ...ffmpeg -re -i h264.mp4 -vcodec copy -f h264 udp://127.0.0.1:1234 # play stream ffplay udp://127.0.0.1:1234 ffplay -f h264 udp://127.0.0.1:1234 -i url (input) input file url ...
  • 本地搭建直播媒体服务 LiveQing直播点播媒体服务下载 利用ffmpeg查看采集设备 ffmpeg -hide_banner -list_devices true -f dshow -i dummy ffmpeg -f dshow -i audio="麦克风阵列 (Realtek High Definition ...
  • FFMPEG读取实时音视频并编码推流-优化版本 //------------------------------------------------------------------------------------------------- 参考链接1、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,305
精华内容 2,122
关键字:

ffmpeg音频推流