开始之初想搞一搞鸿蒙的底层,后来经过实践发现,差点把自己搞泪崩,于是只能换个方向测评了。
早上上班路过公司楼下的时候,发现之前架的摄像头还在,于是到位置上的第一件事情就确认一下这个摄像头是否还能用,于是这篇测评文章诞生了。
首先把系统刷成Ubuntu的版本的,配置各种环境(IP SSH samba 交叉工具链等),在一通操作完成之后,就吧啦吧啦搞代码,ffmpeg库交叉编译。
一、直接用ffplay指令拉流,先看看效果,效果肯定出的来对吧,不太好的是很耗资源,图片有标记
拉出来的视频中间有一小会不太流畅,看视频内容。一路视频不太应该出现这样现象,回头再查查原因。
二、通过程序去拉流保存成视频格式,进行播放,过程如下图
附上相关的代码(电脑的代码出于安全考虑拷贝不出来,找了个差不多的代码粘贴上来)如下:
#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;
}
更多回帖