飞腾派
直播中

lxiangxiang

11年用户 26经验值
擅长:嵌入式技术 模拟技术 控制/MCU
私信 关注
[经验]

【飞腾派4G版免费试用】第三篇:ffmpeg视频拉流

fdb9bfcd3737149ce0be17ecdc32d47b

开始之初想搞一搞鸿蒙的底层,后来经过实践发现,差点把自己搞泪崩,于是只能换个方向测评了。

早上上班路过公司楼下的时候,发现之前架的摄像头还在,于是到位置上的第一件事情就确认一下这个摄像头是否还能用,于是这篇测评文章诞生了。

首先把系统刷成Ubuntu的版本的,配置各种环境(IP SSH samba 交叉工具链等),在一通操作完成之后,就吧啦吧啦搞代码,ffmpeg库交叉编译。

一、直接用ffplay指令拉流,先看看效果,效果肯定出的来对吧,不太好的是很耗资源,图片有标记

1703044348534.png

拉出来的视频中间有一小会不太流畅,看视频内容。一路视频不太应该出现这样现象,回头再查查原因。

二、通过程序去拉流保存成视频格式,进行播放,过程如下图

80ff3de23288ffa5e3ee005ed1e711b.jpg

附上相关的代码(电脑的代码出于安全考虑拷贝不出来,找了个差不多的代码粘贴上来)如下:

#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>

#define __STDC_CONSTANT_MACROS

#ifdef __cplusplus
extern "C" {
#endif

#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
#include <libavcodec/avcodec.h>


#ifdef __cplusplus
}
#endif


static double r2d(AVRational r){
	return r.num == 0 || r.den == 0 ? 0. : (double)r.num / (double)r.den;
}

int main(int argc, char **argv)
{
	AVOutputFormat *ofmt = NULL;
	AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
	AVPacket pkt;
	char in_filename[128] = {0}, out_filename[128] = {0};  
	int ret, i;
	int stream_index = 0;
	int video_index=-1;
	int frame_index=0;
	int I_received = 0;
	int frames_count = -1;

	bool push_stream = false,firstframe_flag=true;
	char *ofmt_name = NULL;

	AVRational frame_rate;
	double duration;

	AVDictionary * opts = NULL;
	long long startTime;


	frames_count=100;
	long long pulltime=100*1000000; //10s

	strcpy(in_filename, "rtsp://guest:Guest123@172.1.64.153:554/h264/ch1/main/av_stream");
	strcpy(out_filename,"2_tt.flv");

	av_register_all();
	avformat_network_init();

	//使用TCP连接打开RTSP,设置最大延迟时间
	AVDictionary *avdic=NULL;  
	char option_key[]="rtsp_transport";  
	char option_value[]="tcp";  
	av_dict_set(&avdic,option_key,option_value,0);  
	char option_key2[]="max_delay";  
	char option_value2[]="5000000";  
	av_dict_set(&avdic,option_key2,option_value2,0); 

	ifmt_ctx = avformat_alloc_context();
	// 1. 打开输入
	// 1.1 读取文件头,获取封装格式相关信息
	if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, &avdic)) < 0) {
		printf("Could not open input file '%s'", in_filename);
		goto end;
	}

	// 1.2 解码一段数据,获取流相关信息
	if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
		printf("Failed to retrieve input stream information");
		goto end;
	}

	//nb_streams代表有几路流,一般是2路:即音频和视频,顺序不一定
	for(i=0;i<ifmt_ctx->nb_streams;i++)
	{
		if(ifmt_ctx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
		{
			//这一路是视频流,标记一下,以后取视频流都从ifmt_ctx->streams[video_index]取
			video_index=i;
			break;
		}
	}

	av_dump_format(ifmt_ctx, 0, in_filename, 0);

	// 2. 打开输出
	// 2.1 分配输出ctx

	if (strstr(out_filename, "rtmp://") != NULL) {
		push_stream = true;
		ofmt_name =(char*)"flv";
	}
	else if (strstr(out_filename, "rtsp://") != NULL) {
		push_stream = true;
		ofmt_name =(char*)"rtsp";
	}
	else if (strstr(out_filename, "udp://") != NULL) {
		push_stream = true;
		ofmt_name = (char*)"mpegts";
	}
	else {
		push_stream = false;
		ofmt_name = NULL;
	}
	avformat_alloc_output_context2(&ofmt_ctx, NULL, ofmt_name, out_filename);
	if (!ofmt_ctx) {
		printf("Could not create output context\n");
		ret = AVERROR_UNKNOWN;
		goto end;
	}

	ofmt = ofmt_ctx->oformat;    

#if 1
	for (i = 0; i < ifmt_ctx->nb_streams; i++) {       
		AVStream *in_stream = ifmt_ctx->streams[i];
		AVCodecParameters *in_codecpar = in_stream->codecpar;

		if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
				in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
				in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
			continue;
		}       

		if (push_stream && (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO)) {
			frame_rate = av_guess_frame_rate(ifmt_ctx, in_stream, NULL);
			duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
		}


		AVCodec *codec = avcodec_find_decoder(in_stream->codecpar->codec_id);	
		AVStream *out_stream = avformat_new_stream(ofmt_ctx,codec);

		if (!out_stream) {
			printf("Failed allocating output stream\n");
			ret = AVERROR_UNKNOWN;
			goto end;
		}

		ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
		if (ret < 0) {
			printf("Failed to copy codec parameters\n");
			goto end;
		}
		out_stream->codecpar->codec_tag = 0;

		if(ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER){
			//   out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

			AVCodecContext *pCodecCtx_out = avcodec_alloc_context3(NULL);  
			avcodec_parameters_to_context(pCodecCtx_out,out_stream->codecpar);
			//AVCodecContext *pCodecCtx_out = avcodec_open2(NULL);  
			//avcodec_parameters_from_context(out_stream->codecpar, pCodecCtx_out);
			pCodecCtx_out->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; 
		}

	}
	//打印一下ofmt_ctx里面的具体信息,最后的1代表是output, 0代表是input
	av_dump_format(ofmt_ctx, 0, out_filename, 1);

	if (!(ofmt->flags & AVFMT_NOFILE)) {    // TODO: 研究AVFMT_NOFILE标志
		// 2.4 创建并初始化一个AVIOContext,用以访问URL(out_filename)指定的资源
		ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
		if (ret < 0) {
			printf("Could not open output file '%s'", out_filename);
			goto end;
		}
	}

	// 3.1 写输出文件头     
	av_dict_set(&opts, "flvflags", "no_duration_filesize", 0);
	ret = avformat_write_header(ofmt_ctx, opts ? &opts : NULL);

	if (ret < 0) {
		printf("Error occurred when opening output file\n");
		goto end;
	}

	startTime = av_gettime(); 	//us

	while (1) {
		AVStream *in_stream, *out_stream;

		// 3.2 从输出流读取一个packet
		ret = av_read_frame(ifmt_ctx, &pkt);
		if (ret < 0) {
			break;
		}

		in_stream  = ifmt_ctx->streams[pkt.stream_index];
		out_stream = ofmt_ctx->streams[pkt.stream_index];

		//此while循环中并非所有packet都是视频帧,当收到视频帧时记录一下,仅此而已
		if(pkt.stream_index==video_index) {
			if ((pkt.flags & AV_PKT_FLAG_KEY) && (I_received == 0)) 
				I_received = 1;
			if (I_received == 0)
				continue; 
			printf("Receive %8d video frames from input URL\n",frame_index);
			frame_index++;
		} else {
			continue;
		}


		if (!push_stream){
			long long pasttime = av_gettime() - startTime;
			if (pasttime>pulltime)
				break;
		}	

		//copy packet
		//转换 PTS/DTS 时序
		if (firstframe_flag){
			firstframe_flag=false;
			pkt.pts =0;
			pkt.dts =0;
		}
		else{
			pkt.pts = av_rescale_q_rnd(pkt.pts,in_stream->time_base,out_stream->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
			pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
		}

		//   printf("pts %d dts %d base %d\n",pkt.pts,pkt.dts, in_stream->time_base);
		pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base); 

		pkt.pos = -1;  

		//视频帧推送速度      
		int codec_type = in_stream->codecpar->codec_type;
		if (push_stream && (codec_type == AVMEDIA_TYPE_VIDEO)) {
			AVRational tb = in_stream->time_base;
			//已经过去的时间
			long long now = av_gettime() - startTime;
			long long dts = 0;
			dts = pkt.dts * (1000 * 1000 * r2d(tb));
			if (dts > now){
				av_usleep(dts - now);
			}
			//       av_usleep((int64_t)(duration*AV_TIME_BASE));
		}

		// 3.4 将packet写入输出
		ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
		if (ret < 0) {
			/**
			  当网络有问题时,容易出现到达包的先后不一致,pts时序混乱会导致
			  av_interleaved_write_frame函数报 -22 错误。暂时先丢弃这些迟来的帧吧
			  若所大部分包都没有pts时序,那就要看情况自己补上时序(比如较前一帧时序+1)再写入。
			  */
			if(ret==-22){
				continue;
			}else{
				printf("Error muxing packet.error code %d\n" , ret);
				break;
			}
		}
		av_packet_unref(&pkt);
	}

	// 3.5 写输出文件尾
	av_write_trailer(ofmt_ctx);
#endif

end:
	av_dict_free(&avdic);
	avformat_close_input(&ifmt_ctx);

	/* close output */
	if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE)) {
		avio_closep(&ofmt_ctx->pb);
	}
	avformat_free_context(ofmt_ctx);

	//  av_freep(&stream_mapping);

	if (ret < 0 && ret != AVERROR_EOF) {
		printf("Error occurred: %d\n", ret);//av_err2str(ret));
		return 1;
	}


	return 0;
}

更多回帖

发帖
×
20
完善资料,
赚取积分