ffmpeg 推流檔案,採用rtmp協議

太極者,無極而生發表於2020-12-02
  • 想看懂這個程式碼需要掌握以下音視訊基礎知識

1、音視訊時間基是什麼?
FFMPEG之TimeBase成員理解
補充:封裝/解封裝、編碼/解碼、採集/顯示這三個層面中的taimeBase是代表不同的意思的,需要通過轉換才可以進行上下層的互動。

2、ffmpeg中對AVRational結構體理解
ffmpeg # AVRational
假如一秒25幀就是1/25;那麼換算成每毫秒25幀就是1000* 1 /25 = 1000/25;換算成每微妙25幀話就是1000*1000/25(每一毫秒等於1000微妙)。

3、PTS和DTS是什麼?
PTS:顯示的時間;
DTS:編碼/解碼時間;
音訊在時間軸是連續的,所以PTS和DTS是一樣的;
視訊中因為有I、B、P幀,I是關鍵幀,P是向前預測,B是向前向後預測所以當沒有B幀時候PTS和DTS在時間軸是一樣的,為什麼有了B幀就就不一樣的呢,因為B幀要等待其他幀解碼出來後才知道這個B幀是 多少。
I、B、P幀主要是為了更拼命努力壓縮空間的。

4、rtmp協議
rtmp協議詳細內容
rtmp協議詳細內容2
絕大多數的流媒體推流都會採用rtmp協議的。

5、flv格式
flv格式解析
檔案推流時候有的格式不支援,是因為伺服器那邊解析不了這種格式。想推流特殊的格式可以在伺服器裡面新增特殊檔案的解析。

extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/time.h"
}
#include <iostream>
using namespace std;
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")

int XError(int errNum)
{
	char buf[1024] = { 0 };
	av_strerror(errNum, buf, sizeof(buf));
	cout << buf << endl;
	getchar();
	return -1;
}
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[])
{

	char *inUrl = "test.flv";
	char *outUrl = "rtmp://192.168.1.102/live";

	//初始化所有封裝和解封裝 flv mp4 mov mp3
	av_register_all();

	//初始化網路庫
	avformat_network_init();

	//
	//輸入流 1 開啟檔案,解封裝
	//輸入封裝上下文
	AVFormatContext *ictx = NULL;

	//開啟檔案,解封檔案頭
	int re = avformat_open_input(&ictx, inUrl, 0, 0);
	if (re != 0)
	{
		return XError(re);
	}
	cout << "open file " << inUrl << " Success." << endl;

	//獲取音訊視訊流資訊 ,h264 flv
	re = avformat_find_stream_info(ictx, 0);
	if (re != 0)
	{
		return XError(re);
	}
	av_dump_format(ictx, 0, inUrl, 0);
	//


	//
	//輸出流 

	//建立輸出流上下文
	AVFormatContext *octx = NULL;
	re = avformat_alloc_output_context2(&octx, 0, "flv", outUrl);
	if (!octx)
	{
		return XError(re);
	}
	cout << "octx create success!" << endl;

	//配置輸出流
	//遍歷輸入的AVStream
	for (int i = 0; i < ictx->nb_streams; i++)
	{
		//建立輸出流
		AVStream *out = avformat_new_stream(octx, ictx->streams[i]->codec->codec);
		if (!out)
		{
			return XError(0);
		}
		//複製配置資訊,同於MP4
		//re = avcodec_copy_context(out->codec, ictx->streams[i]->codec);
		re = avcodec_parameters_copy(out->codecpar, ictx->streams[i]->codecpar);
		out->codec->codec_tag = 0;
	}
	av_dump_format(octx, 0, outUrl, 1);
	//


	//rtmp推流

	//開啟io
	re = avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE);
	if (!octx->pb)
	{
		return XError(re);
	}

	//寫入頭資訊
	re = avformat_write_header(octx, 0);
	if (re < 0)
	{
		return XError(re);
	}
	cout << "avformat_write_header " << re << endl;
	AVPacket pkt;
	long long startTime = av_gettime();
	for (;;)
	{
		re = av_read_frame(ictx, &pkt);
		if (re != 0)
		{
			break;
		}
		cout << pkt.pts << " " << flush;
		//計算轉換pts dts
		AVRational itime = ictx->streams[pkt.stream_index]->time_base;
		AVRational otime = octx->streams[pkt.stream_index]->time_base;
		pkt.pts = av_rescale_q_rnd(pkt.pts, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
		pkt.dts = av_rescale_q_rnd(pkt.dts, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
		pkt.duration = av_rescale_q_rnd(pkt.duration, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
		pkt.pos = -1;

		//視訊幀推送速度
		if (ictx->streams[pkt.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			AVRational tb = ictx->streams[pkt.stream_index]->time_base;
			//已經過去的時間
			long long now = av_gettime() - startTime;
			long long pts = 0;
			pts = pkt.pts * (1000 * 1000 * r2d(tb));
			if (pts > now)
				av_usleep(pts - now);
		}

		re = av_interleaved_write_frame(octx, &pkt);
		if (re<0)
		{
			return XError(re);
		}
	}

	cout << "file to rtmp test" << endl;
	getchar();
	return 0;
}

相關文章