FFmpeg —— RTMP推流到流媒体服务器(编码的方式)—— 屏幕录像并推流_网页录屏转流媒体-程序员宅基地

技术标签: 音视频  FFmpeg  流媒体  ffmpeg  

流媒体服务器的搭建,可以参考这篇文章

centos7+nginx+rtmp+ffmpeg搭建流媒体服务器

基于命令行的方式推流可以参考这篇文章

FFmpeg —— 屏幕录像和录音并推流(命令行的方式)

本篇是基于代码的方式来实现的。

屏幕录像并推流

#include <stdio.h>

#define __STDC_CONSTANT_MACROS

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "libavutil/imgutils.h"
#include "libavutil/dict.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
};

int main()
{
	AVFormatContext *ifmtCtx = NULL;
	AVFormatContext *ofmtCtx = NULL;
	AVPacket pkt;
	AVFrame *pFrame, *pFrameYUV;
	SwsContext *pImgConvertCtx;
	AVDictionary *params = NULL;
	AVCodec *pCodec;
	AVCodecContext *pCodecCtx;
	unsigned char *outBuffer;
	AVCodecContext *pH264CodecCtx;
	AVCodec *pH264Codec;

	int ret = 0;
	unsigned int i = 0;
	int videoIndex = -1;
	int frameIndex = 0;

	const char *inFilename = "desktop";//输入URL
	const char *outFilename = "rtmp://192.168.31.29/myapp/test"; //输出URL
	const char *ofmtName = NULL;

	avdevice_register_all();
	avformat_network_init();

	AVInputFormat *ifmt = av_find_input_format("gdigrab");
	if (!ifmt)
	{
		printf("can't find input device\n");
		goto end;
	}

	// 1. 打开输入
	// 1.1 打开输入文件,获取封装格式相关信息
	if ((ret = avformat_open_input(&ifmtCtx, inFilename, ifmt, 0)) < 0)
	{
		printf("can't open input file: %s\n", inFilename);
		goto end;
	}

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

	// 1.3 获取输入ctx
	for (i=0; i<ifmtCtx->nb_streams; ++i)
	{
		if (ifmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoIndex = i;
			break;
		}
	}

	printf("%s:%d, videoIndex = %d\n", __FUNCTION__, __LINE__, videoIndex);

	av_dump_format(ifmtCtx, 0, inFilename, 0);

	// 1.4 查找输入解码器
	pCodec = avcodec_find_decoder(ifmtCtx->streams[videoIndex]->codecpar->codec_id);
	if (!pCodec)
	{
		printf("can't find codec\n");
		goto end;
	}

	pCodecCtx = avcodec_alloc_context3(pCodec);
	if (!pCodecCtx)
	{
		printf("can't alloc codec context\n");
		goto end;
	}

	avcodec_parameters_to_context(pCodecCtx, ifmtCtx->streams[videoIndex]->codecpar);

	//  1.5 打开输入解码器
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
	{
		printf("can't open codec\n");
		goto end;
	}


	// 1.6 查找H264编码器
	pH264Codec = avcodec_find_encoder(AV_CODEC_ID_H264);
	if (!pH264Codec)
	{
		printf("can't find h264 codec.\n");
		goto end;
	}

	// 1.6.1 设置参数
	pH264CodecCtx = avcodec_alloc_context3(pH264Codec);
	pH264CodecCtx->codec_id = AV_CODEC_ID_H264;
	pH264CodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
	pH264CodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
	pH264CodecCtx->width = pCodecCtx->width;
	pH264CodecCtx->height = pCodecCtx->height;
	pH264CodecCtx->time_base.num = 1;
	pH264CodecCtx->time_base.den = 25;	//帧率(即一秒钟多少张图片)
	pH264CodecCtx->bit_rate = 400000;	//比特率(调节这个大小可以改变编码后视频的质量)
	pH264CodecCtx->gop_size = 250;
	pH264CodecCtx->qmin = 10;
	pH264CodecCtx->qmax = 51;
	//some formats want stream headers to be separate
//	if (pH264CodecCtx->flags & AVFMT_GLOBALHEADER)
	{
		pH264CodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
	}


	// 1.7 打开H.264编码器
	av_dict_set(&params, "preset", "superfast", 0);
	av_dict_set(&params, "tune", "zerolatency", 0);	//实现实时编码
	if (avcodec_open2(pH264CodecCtx, pH264Codec, &params) < 0)
	{
		printf("can't open video encoder.\n");
		goto end;
	}

	// 2. 打开输出
	// 2.1 分配输出ctx
	if (strstr(outFilename, "rtmp://"))
	{
		ofmtName = "flv";
	}
	else if (strstr(outFilename, "udp://"))
	{
		ofmtName = "mpegts";
	}
	else
	{
		ofmtName = NULL;
	}

	avformat_alloc_output_context2(&ofmtCtx, NULL, ofmtName, outFilename);
	if (!ofmtCtx)
	{
		printf("can't create output context\n");
		goto end;
	}

	// 2.2 创建输出流
	for (i=0; i<ifmtCtx->nb_streams; ++i)
	{
		AVStream *outStream = avformat_new_stream(ofmtCtx, NULL);
		if (!outStream)
		{
			printf("failed to allocate output stream\n");
			goto end;
		}

		avcodec_parameters_from_context(outStream->codecpar, pH264CodecCtx);
	}

	av_dump_format(ofmtCtx, 0, outFilename, 1);

	if (!(ofmtCtx->oformat->flags & AVFMT_NOFILE))
	{
		// 2.3 创建并初始化一个AVIOContext, 用以访问URL(outFilename)指定的资源
		ret = avio_open(&ofmtCtx->pb, outFilename, AVIO_FLAG_WRITE);
		if (ret < 0)
		{
			printf("can't open output URL: %s\n", outFilename);
			goto end;
		}
	}

	// 3. 数据处理
	// 3.1 写输出文件
	ret = avformat_write_header(ofmtCtx, NULL);
	if (ret < 0)
	{
		printf("Error accourred when opening output file\n");
		goto end;
	}


	pFrame = av_frame_alloc();
	pFrameYUV = av_frame_alloc();

	outBuffer = (unsigned char*) av_malloc(
			av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width,
					pCodecCtx->height, 1));
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer,
			AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

	pImgConvertCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
			pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
			AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);


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

		if (pkt.stream_index == videoIndex)
		{
			ret = avcodec_send_packet(pCodecCtx, &pkt);
			if (ret < 0)
			{
				printf("Decode error.\n");
				goto end;
			}

			if (avcodec_receive_frame(pCodecCtx, pFrame) >= 0)
			{
				sws_scale(pImgConvertCtx,
						(const unsigned char* const*) pFrame->data,
						pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data,
						pFrameYUV->linesize);


				pFrameYUV->format = pCodecCtx->pix_fmt;
				pFrameYUV->width = pCodecCtx->width;
				pFrameYUV->height = pCodecCtx->height;

				ret = avcodec_send_frame(pH264CodecCtx, pFrameYUV);
				if (ret < 0)
				{
					printf("failed to encode.\n");
					goto end;
				}

				if (avcodec_receive_packet(pH264CodecCtx, &pkt) >= 0)
				{
					// 设置输出DTS,PTS
			        pkt.pts = pkt.dts = frameIndex * (ofmtCtx->streams[0]->time_base.den) /ofmtCtx->streams[0]->time_base.num / 25;
			        frameIndex++;

					ret = av_interleaved_write_frame(ofmtCtx, &pkt);
					if (ret < 0)
					{
						printf("send packet failed: %d\n", ret);
					}
					else
					{
						printf("send %5d packet successfully!\n", frameIndex);
					}
				}
			}
		}

		av_packet_unref(&pkt);
	}

    av_write_trailer(ofmtCtx);

end:
    avformat_close_input(&ifmtCtx);

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

    if (ret < 0 && ret != AVERROR_EOF) {
        printf("Error occurred\n");
        return -1;
    }

    return 0;
}

 问题总结

调用av_interleaved_write_frame失败,返回值-10053

解决办法:

设置AV_CODEC_FLAG_GLOBAL_HEADER

    //some formats want stream headers to be separate
//    if (pH264CodecCtx->flags & AVFMT_GLOBALHEADER)
    {
        pH264CodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/guoyunfei123/article/details/106173310

智能推荐

L1-014. 简单题_输出事实this-程序员宅基地

文章浏览阅读3.1w次。https://www.patest.cn/contests/gplt/L1-014这次真的没骗你 —— 这道超级简单的题目没有任何输入。你只需要在一行中输出事实:“This is a simple problem.”就可以了。#include &lt;iostream&gt;#include &lt;cstdio&gt;using namespace std;int main(){..._输出事实this

Multipathd Daemon was Unable to Set Options "fast_io_fail_tmo" or "dev_loss_tmo" Under UEK1 or RHCK_multipath rport failed to read dev_loss_tmo value -程序员宅基地

文章浏览阅读421次。Multipathd Daemon was Unable to Set Options "fast_io_fail_tmo" or "dev_loss_tmo" Under UEK1 or RHCK (文档 ID 1678794.1) APPLIES TO:Linux OS - Version Oracle Linux 5.7 with Unbreakable Enterprise Ker..._multipath rport failed to read dev_loss_tmo value error 2

Jython_Jython Development Tools (JyDT) for Eclipse_jythontools-程序员宅基地

文章浏览阅读729次。Installing JyDTThis section describes how to install JyDT by connecting to the JyDT update siteon the internet. If you have downloaded an update site to a computer on your network, please follow the_jythontools

验证结构中IMonitor的作用_验证 monitor组件的主要功能-程序员宅基地

文章浏览阅读922次。DUT 的 input 端口采用的monitor 是干嘛的呢?根据白书: 一,大型项目中,driver 根据协议发送数据,而monitor根据协议接收数据。如果driver和monitor由不同人实现,那么可以大大减少其中任意一方对协议理解的错误。二,便于复用???另外,我的一点理解是:用于判断是否真的将输入,打入了DUT。????就一般环境,IF与dri_验证 monitor组件的主要功能

kubernetes apiserver 报错 service-account-issuer is a required flag-程序员宅基地

文章浏览阅读1.8k次。k8s kube-apiserver 启动报错k8s 版本 1.24根据报错提示说的是是一个必须的参数我们来看一下这个参数是干啥的服务帐号令牌颁发者的标识符。 颁发者将在已办法令牌的 “iss” 声明中检查此标识符。 此值为字符串或 URI。 如果根据 OpenID Discovery 1.0 规范检查此选项不是有效的 URI,则即使特性门控设置为 true, ServiceAccountIssuerDiscovery 功能也将保持禁用状态。 强烈建议该值符合 OpenID 规范: https://_service-account-issuer

【CodeForces - 988C 】Equal Sums (思维,STLmap,STLset,tricks)_草莓^app^【755c.top】最新版-程序员宅基地

文章浏览阅读288次。题干:You are given kk sequences of integers. The length of the ii-th sequence equals to nini.You have to choose exactly two sequences ii and jj (i≠ji≠j) such that you can remove exactly one element ..._草莓^app^【755c.top】最新版

随便推点

Windows下Cygwin环境的Hadoop安装(4)- 在Eclipse中建立hadoop开发环境_hadoop class path 多个文件 cygwin-程序员宅基地

文章浏览阅读6.3k次。在使用hadoop的过程中,不可避免地遇到一些问题需要对hadoop代码进行改进,这就要求我们必须建立一个可修改hadoop代码的开发环境,下面的过程,我们就来建立一个基于Eclipse的hadoop开发环境。- 安装AntHadoop的编译需要Ant的支持,从这里下载并安装最新的Ant:http://ant.apache.org/bindownload.cgi。安装完成后,别忘了将_hadoop class path 多个文件 cygwin

VB:所有控件自适应窗口大小_控件随窗体大小变化改变 vb代码-程序员宅基地

文章浏览阅读1.1w次,点赞5次,收藏18次。Option ExplicitPrivate FormOldWidth As Long'保存窗体的原始宽度Private FormOldHeight As Long'保存窗体的原始高度'在调用ResizeForm前先调用本函数Private Sub ResizeInit(FormNameAs Form)Dim Obj As Control_控件随窗体大小变化改变 vb代码

Python学习笔记——pandas中get_dummies()的用法_python get_dummies-程序员宅基地

文章浏览阅读5.5k次,点赞5次,收藏14次。原文链接在此可以看到get_dummies默认就是所有变量都转了默认转了以后的变量用columns名_数值名表示其中参数predix:prefix : 给输出的列添加前缀,如prefix=“A”,输出的列会显示类似prefix_sep : 设置前缀跟分类的分隔符sepration,默认是下划线"_"df = pd.DataFrame([[‘green’ , ‘A’],[‘red’ , ‘B’],[‘blue’ , ‘A’]])pd.get_dummies(df,prefix = _python get_dummies

【政考网答疑】为什么公务员招录限制35岁以下?-程序员宅基地

文章浏览阅读934次。政考网每日一答,今日咱们讨论的问题是为什么公务员招录限制35岁以下?众所周知,无论是各地省考还是国考,均会要求考生年龄在18周岁以上、35周岁以下(应届硕士和博士经招录机关同意,可放宽到40岁),那么,公务员招录考试为何会限制35岁以下报考呢?这样的要求是否合理?1、高龄人员的可塑性相对不强相比较应届毕业生或者刚毕业不久的大学生,35以上的考生在身体素质上的优势并不明显,特别是一些基层岗位,条件艰苦,高龄考生的岗位匹配度相对较低。古语云,“三十而立,四十而不惑。”高龄考生已从过...

MySQL必知必会学习历程(一)_mysql编写新增教育经历-程序员宅基地

文章浏览阅读2.1k次,点赞5次,收藏27次。MySQL必知必会学习历程第1章 了解SQL1.1 关键术语介绍第2章 MySQL简介2.1 关键术语介绍2.2 安装mysql命令行实用程序(可选)2.3 安装mysql_community(最优)2.3.1 下载mysql_community2.3.2 安装过程第3章 使用MySQL3.1 连接到数据库3.2 建立样例数据库3.2.1 创建空的数据库3.2.2 下载样例表生成脚本3.2.3 使用样例表生成脚本3.3 选择数据库(USE)3.4 了解数据库和表(SHOW)3.5 命令及注释汇总第4章 检索_mysql编写新增教育经历

你都用 Python 来做什么?_你用python做什么-程序员宅基地

文章浏览阅读1.3w次,点赞42次,收藏158次。你们都用python做些什么呢?在开发中 python 这一个语言就像是小叮当,而 python 的第三方库则是“百宝箱”,你只要想着对某一个方向进行开发,那么这个“百宝箱”就会给你想要的东西。由于我是在开发多年后接触到的 python,对我来说自从接触 python 就打开了“新世界”的大门。(我正在求设计做一张图,还没做完,做完贴上在这里)一、做个自己玩游戏的程序在前几年,我有一个朋友是一个“游戏商人”,不过大多数他是手动进行商品处理。他有一天找到我,跟我说“嗨兄弟,会不会做外G?”!此时我_你用python做什么