1. 程式人生 > >ffmpeg用程式碼實現自己的muxer

ffmpeg用程式碼實現自己的muxer

1、概述

本程式碼實現了一個muxer並加入到ffmpeg的AVOutputFormat 連結串列中去,使程式碼能直接呼叫。實現的意義一是瞭解ffmpeg程式碼結構,二是可以自己整自己的視訊格式,讓別人播放不了。

2、程式碼

簡單看下程式碼:

/*
*本程式主要實現一個自己的muxer並加入到muxer鏈中去,使其可用,只有視訊
*作者:繆國凱(MK)
*[email protected]
*2015-6-2
*/

#include "stdafx.h"

#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#
#ifdef __cplusplus
};
#endif

#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
//#pragma comment(lib, "avdevice.lib")
//#pragma comment(lib, "avfilter.lib")
//#pragma comment(lib, "postproc.lib")
//#pragma comment(lib, "swresample.lib")
//#pragma comment(lib, "swscale.lib")

int mk_write_header(struct AVFormatContext *fmt)
{
	//這些地方就可以加入自己的格式定義
	return 0;
}

int mk_write_packet(struct AVFormatContext *fmt, AVPacket *pkt)
{
	avio_write(fmt->pb, pkt->data, pkt->size);//簡單的用file協議寫檔案
	return 0;
}

int mk_write_trailer(struct AVFormatContext *fmt)
{
	//這些地方就可以加入自己的格式定義
	return 0;
}

AVOutputFormat ff_mk_muxer = 
{
	/*.name			= */"mk",
	/*.long_name		= */"mk (MK Video Container)",
	/*.mime_type		= */"mkvideo/x-msvideo",
	/*.extensions		= */"mk",

	/*.audio_codec		= */AV_CODEC_ID_NONE,
	/*.video_codec		= */AV_CODEC_ID_RAWVIDEO,//這裡先用ffmpeg自帶的yuv編碼器,以後改成自己的
	/*.subtitle_codec	= */AV_CODEC_ID_NONE,

	/*.flags		= */AVFMT_NOTIMESTAMPS,
	/*.codec_tag		= */NULL,
	/*.priv_class		= */NULL,
	/*.next				= */NULL,
	/*.priv_data_size	= */0,	

	/*.write_header		= */mk_write_header,
	/*.write_packet		= */mk_write_packet,
	/*.write_trailer	= */mk_write_trailer,
};

void help()
{
	printf("**********************************************\n");
	printf("Usage:\n");
	printf("    MyMuxer [inputfile] [outputfile.mk]\n");
	printf("\n");
	printf("Examples: \n");
	printf("    MyMuxer a.avi a.mk\n");
	printf("**********************************************\n");  
}

int _tmain(int argc, _TCHAR* argv[])
{
	if(argc < 3 || (!strcmp(argv[1],"--help")))
	{
		help();
		return 0;
	}

	AVFormatContext *in_fxt = NULL, *out_fxt = NULL;
	AVStream *out_stream = NULL;
	int video_index = -1;

	av_register_all();
	av_register_output_format(&ff_mk_muxer);//把自己的muxer加入連結串列

	if (avformat_open_input(&in_fxt, argv[1], NULL, NULL) < 0)
	{
		printf("can not open the input file context!\n");
		goto end;
	}
	if (avformat_find_stream_info(in_fxt, NULL) < 0)
	{
		printf("can not find the stream info!\n");
		goto end;
	}

	if(avformat_alloc_output_context2(&out_fxt, NULL, NULL, argv[2]) < 0)
	{
		printf("can not alloc output context!\n");
		goto end;
	}

	for (int i = 0; i < in_fxt->nb_streams; i++)
	{
		if (in_fxt->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			//open decoder
			if(0 > avcodec_open2(in_fxt->streams[i]->codec, avcodec_find_decoder(in_fxt->streams[i]->codec->codec_id), NULL))
			{
				printf("can not find or open decoder!\n");
				goto end;
			}
			video_index = i;
			//new stream
			out_stream = avformat_new_stream(out_fxt, NULL);
			if (!out_stream)
			{
				printf("can not new stream for output!\n");
				goto end;
			}
			//set codec context param
			out_stream->codec->codec = avcodec_find_encoder(out_fxt->oformat->video_codec);
			out_stream->codec->height = in_fxt->streams[i]->codec->height;
			out_stream->codec->width = in_fxt->streams[i]->codec->width;

			out_stream->codec->time_base.num = in_fxt->streams[i]->avg_frame_rate.den;
			out_stream->codec->time_base.den = in_fxt->streams[i]->avg_frame_rate.num;

			out_stream->codec->sample_aspect_ratio = in_fxt->streams[i]->codec->sample_aspect_ratio;			
			out_stream->codec->pix_fmt = in_fxt->streams[i]->codec->pix_fmt;
			if (!out_stream->codec->codec)
			{
				printf("can not find the encoder!\n");
				goto end;
			}
			if ((avcodec_open2(out_stream->codec, out_stream->codec->codec, NULL)) < 0)
			{
				printf("can not open the encoder\n");
				goto end;
			}
			if (out_fxt->oformat->flags & AVFMT_GLOBALHEADER)
				out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
			break;
		}
	}

	if (-1 == video_index)
	{
		printf("found no video stream in input file!\n");
		goto end;
	}

	if (!(out_fxt->oformat->flags & AVFMT_NOFILE))
	{
		if(avio_open(&out_fxt->pb, argv[2], AVIO_FLAG_WRITE) < 0)
		{
			printf("can not open output file handle!\n");
			goto end;
		}
	}

	if(avformat_write_header(out_fxt, NULL) < 0)
	{
		printf("can not write the header of the output file!\n");
		goto end;
	}

	AVPacket pkt_in, pkt_out;
	AVFrame *frame;
	frame = av_frame_alloc();
	av_init_packet(&pkt_in);
	av_init_packet(&pkt_out);
	int got_frame, got_picture;
	int i = 0, frame_index = 0;
	while(1)
	{
		got_frame = -1;
		got_picture = -1;
		if (av_read_frame(in_fxt, &pkt_in) < 0)
		{
			break;
		}
		if (avcodec_decode_video2(in_fxt->streams[video_index]->codec, frame, &got_frame, &pkt_in) < 0)
		{
			printf("can not decoder a frame");
			break;
		}
		av_free_packet(&pkt_in);

		if (got_frame)
		{
			frame->pts = i++;
			pkt_out.data = NULL;//主要這裡必須自己初始化,或者必須置為null,不然ff_alloc_packet2函式會報錯
			pkt_out.size = 0;
			if (avcodec_encode_video2(out_stream->codec, &pkt_out, frame, &got_picture) < 0)
			{
				printf("can not encode a frame!\n");
				break;
			}

			if (got_picture)
			{
				printf("Succeed to encode frame: %5d\tsize:%5d\n",frame_index,pkt_out.size);
				pkt_out.stream_index = out_stream->index;
				frame_index++;
				av_write_frame(out_fxt, &pkt_out);
				av_free_packet(&pkt_out);
			}
		}
	}
	av_frame_free(&frame);

	av_write_trailer(out_fxt);

	//clean
	avcodec_close(out_stream->codec);
	avcodec_close(out_fxt->streams[video_index]->codec);
end:
	avformat_close_input(&in_fxt);

	if (out_fxt && !(out_fxt->oformat->flags & AVFMT_NOFILE))
	{
		avio_close(out_fxt->pb);
	}
	avformat_free_context(out_fxt);

	return 0;
}


3、解釋

簡單說下基本原理。

首先自己實現一個AVOutputFormat,然後把這個muxer加入到連結串列中去,其他的根本不用自己管了,ffmpeg這套框架做的的確很漂亮,會一層層的掉用到你的muxer來,函式avformat_alloc_output_context2會根據輸入的格式或字尾來在AVOutputFormat鏈中查詢最適合的muxer,並把這個muxer的指標用oformat儲存,下面看看格式匹配的程式碼:

AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
                                const char *mime_type)
{
    AVOutputFormat *fmt = NULL, *fmt_found;
    int score_max, score;

    /* specific test for image sequences */
#if CONFIG_IMAGE2_MUXER
    if (!short_name && filename &&
        av_filename_number_test(filename) &&
        ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) {
        return av_guess_format("image2", NULL, NULL);
    }
#endif
    /* Find the proper file type. */
    fmt_found = NULL;
    score_max = 0;
    while ((fmt = av_oformat_next(fmt))) {
        score = 0;
        if (fmt->name && short_name && av_match_name(short_name, fmt->name))
            score += 100;
        if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))
            score += 10;
        if (filename && fmt->extensions &&
            av_match_ext(filename, fmt->extensions)) {
            score += 5;
        }
        if (score > score_max) {
            score_max = score;
            fmt_found = fmt;
        }
    }
    return fmt_found;
}
遍歷整個連結串列,得分最高的就是最匹配的格式,name和傳入的shotname(就是傳入的格式)匹配得分100,mime_type匹配得分10,extensions和傳入的檔名字尾匹配得分5,是不是很有意思。

再來看看avformat_write_header的程式碼:

int avformat_write_header(AVFormatContext *s, AVDictionary **options)
{
    int ret = 0;

    if (ret = init_muxer(s, options))
        return ret;

    if (s->oformat->write_header) {
        ret = s->oformat->write_header(s);
        if (ret >= 0 && s->pb && s->pb->error < 0)
            ret = s->pb->error;
        if (ret < 0)
            return ret;
        if (s->flush_packets && s->pb && s->pb->error >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS)
            avio_flush(s->pb);
    }

    if ((ret = init_pts(s)) < 0)
        return ret;

    if (s->avoid_negative_ts < 0) {
        av_assert2(s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO);
        if (s->oformat->flags & (AVFMT_TS_NEGATIVE | AVFMT_NOTIMESTAMPS)) {
            s->avoid_negative_ts = 0;
        } else
            s->avoid_negative_ts = AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE;
    }

    return 0;
}
會呼叫oformat的write_header。

再看av_write_frame的程式碼:
int av_write_frame(AVFormatContext *s, AVPacket *pkt)
{
    int ret;

    ret = check_packet(s, pkt);
    if (ret < 0)
        return ret;

    if (!pkt) {
        if (s->oformat->flags & AVFMT_ALLOW_FLUSH) {
            ret = s->oformat->write_packet(s, NULL);
            if (s->flush_packets && s->pb && s->pb->error >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS)
                avio_flush(s->pb);
            if (ret >= 0 && s->pb && s->pb->error < 0)
                ret = s->pb->error;
            return ret;
        }
        return 1;
    }

    ret = compute_pkt_fields2(s, s->streams[pkt->stream_index], pkt);

    if (ret < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
        return ret;

    ret = write_packet(s, pkt);
    if (ret >= 0 && s->pb && s->pb->error < 0)
        ret = s->pb->error;

    if (ret >= 0)
        s->streams[pkt->stream_index]->nb_frames++;
    return ret;
}

會呼叫oformat的write_packet。

最後看看av_write_trailer的程式碼:

int av_write_trailer(AVFormatContext *s)
{
    int ret, i;

    for (;; ) {
        AVPacket pkt;
        ret = interleave_packet(s, &pkt, NULL, 1);
        if (ret < 0)
            goto fail;
        if (!ret)
            break;

        ret = write_packet(s, &pkt);
        if (ret >= 0)
            s->streams[pkt.stream_index]->nb_frames++;

        av_free_packet(&pkt);

        if (ret < 0)
            goto fail;
        if(s->pb && s->pb->error)
            goto fail;
    }

fail:
    if (s->oformat->write_trailer)
        if (ret >= 0) {
        ret = s->oformat->write_trailer(s);
        } else {
            s->oformat->write_trailer(s);
        }

    if (s->pb)
       avio_flush(s->pb);
    if (ret == 0)
       ret = s->pb ? s->pb->error : 0;
    for (i = 0; i < s->nb_streams; i++) {
        av_freep(&s->streams[i]->priv_data);
        av_freep(&s->streams[i]->index_entries);
    }
    if (s->oformat->priv_class)
        av_opt_free(s->priv_data);
    av_freep(&s->priv_data);
    return ret;
}

會呼叫oformat的write_trailer。

所以說,只要你按照ffmpeg的格式去定義muxer並加入連結串列,其他的都不用管了。

 最後驗證一下你的結果,執行程式隨便轉一個結果出來,把字尾.mk改為.yuv,用yuv播放器放一下,ok了,大功告成。下一篇介紹如何把這個muxer加入ffmpeg原始碼,使ffmpeg命令列可以用這個muxer。

相關推薦

ffmpeg程式碼實現自己muxer

1、概述 本程式碼實現了一個muxer並加入到ffmpeg的AVOutputFormat 連結串列中去,使程式碼能直接呼叫。實現的意義一是瞭解ffmpeg程式碼結構,二是可以自己整自己的視訊格式,讓別人播放不了。 2、程式碼 簡單看下程式碼: /* *本程式主要實現一個自

https是如何加密的 (知道了原理之後,希望自己程式碼實現一下,還有用於對個人資訊和公鑰進行加密的雜湊演算法,有時間也去查一下)

由於http協議是明文傳輸資料,資料的安全性沒有保障。為了改進這種明文傳輸協議,https誕生了。   https是在應用層和傳輸層之間,增加了一層ssl加密。對於加密,請往下看:   1、對稱加密   每次在傳送資料之前,伺服器先生成一把金鑰,

通過編譯時註解生成程式碼實現自己的ButterKnife

背景概述 註解的處理除了可以在執行時通過反射機制處理外,還可以在編譯期進行處理。 Java5中提供了apt工具來進行編譯期的註解處理。apt是命令列工具,與之配套的是一套描述“程式在編譯時刻的靜態結構”的API:Mirror API(com.sun.mirr

Unity 程式碼實現建立資源

用程式碼加圖片資源,建立材質 //圖片物件 Texture2D texture = null; void Start() { //建立材質,並設定預設著色器 Material mat = new Material (Shader.Find("Tran

java程式碼實現高併發

這裡申請了1000個執行緒,並且執行1000次任務。當所有任務完成後,main退出。 publicclass Test {       publicstaticvoid main(String[] 

VC++教你程式碼實現把拷貝的內容放到剪貼簿。

以下是一個例項,將report控制元件拷貝下來的內容放到剪貼簿,作為文字列拷貝。 /** コピーコマンドの処理関數.  * @param なし.  * @return なし.  * @exception なし.  */ void CGTDTemplatePropDlg::m

程式碼實現GridLayout平均分佈樣式

本人發帖宗旨: 1.不長篇大論。 2.發文前簡單搜一下是不是有人發過(簡單的)。 3.直說知識點,基礎知識請自行補習。 4.隨性 前提:使用android.support.v7.widget.GridLayout包,相容性強了不少。用的Layout I

Android進階——自定義View之繼承TextView巧DrawableLeft實現自己的CheckableTextView

引言 Android自帶的許多控制元件已經十分強大,甚至很多功能都已經有現成的控制元件去使用了,不過介面效果是肯定會打折扣的,幸好android控制元件自身的擴充套件性十分優秀,很多時候我們只需要簡單繼承下現有控制元件擴充套件些許功能就能得到一個全新的控制元件

程式碼實現輸入i am a student ,輸出 student a am i,不能類庫函式

1.輸入i am a student ,輸出student a am i,不能用類庫函式 public static void main(String[]args){ String s1 = "i am a student"; //獲取到擷取後的字串陣列 String[

一個ArrayList物件aList中存有若干個字串元素,現欲遍歷該ArrayList物件,刪除其中所有值為"abc"的字串元素,請程式碼實現

package com.CSDN;import java.util.ArrayList;/* * 一個ArrayList物件aList中存有若干個字串元素, * 現欲遍歷該ArrayList物件,刪除其中所有值為"abc"的字串元素,請用程式碼實現。 * 思路: * 1.定義一個靜態方法,分別傳入引數

程式碼實現以下需求

(1)定義學生類,包含姓名(String name),性別(String gender),年齡(int age)三個屬性,生成空參有參構造,set和get方法,toString方法(2)鍵盤錄入6個學員資訊(錄入格式:張三,男,25),要求有兩個相同的資訊,將6個學員資訊存

程式碼證明自己閒的蛋疼(三)——回溯法做數獨

數獨大家應該都玩過,1~9數字,滿足每一行、每一列、每一個粗線宮(3*3)內的數字均含1-9,不重複。 起始會有一些給定的值,然後我們去填剩餘的數,一個合理的數獨最終解一定是唯一的。 九日哥也很喜歡玩數獨,喜歡到最後都懶得算了,直接寫個程式搜出答案。。。 其實想一想,

程式碼實現註冊Ocx和Dll

在windows系統中,可以通過Regsvr32來實現註冊ocx或者dl, 程式設計時,呼叫Regsvr32來註冊,卻不能正常執行。尤其是在Win7系統中,需要管理員身份才能執行。 使用下面的程式碼則能正常註冊。 //註冊 function RegisterDllServer

shell實現一個小指令碼,來同來統計自己某個檔案下的程式碼,總的程式碼行數,總的註釋量,總的空行量?支援遍歷查詢,支援軟連結查詢

[[email protected] yunwei]# cat sum_code_row_version1.4.sh #!/bin/bash # File Name: sum_code_row.sh # Author: Liwqiang # mail: [email

python實現一段程式碼,它的功能是將自己列印

今天看到一道有趣的面試題,用python實現一段程式碼,然後將自己列印,其實很簡單 import sys   首先匯入 sys 模組 f_name = sys.argv[0]              sys.argv[ ]第一個元素是程式本身,sys.argv[0] 

堆排(一)——自己程式碼實現

很激動,竟然有人看我的文章首先感謝liuyubobobo的程式碼,點選開啟連結我決定好好把堆這一塊的知識再補充一下堆的三個要點:①建堆——從第一個非葉子節點開始,從後往前,將每一個小堆都變成最大堆(這是

FFmpeg總結(十二)ffmpeg與nginx實現直播多路流並發播放

xxx 開源 conf ref itl rect arc med rtm 圖:撒哈拉沙漠 下載 nginx 和 nginx-rtmp源碼: http://nginx.org/download/nginx-1.5.10.tar.gz https://github.com/a

ffmpeg和SDL2實現linux player

rec null sizeof open type error yuv text idt 一、version 1#include <stdio.h> #include <libavcodec/avcodec.h> #include <lib

實現自己的大發888平臺開發框架(采Java Socket)

exe provide lang 輸出流 reflect cati trace 客戶端 hand 大發888平臺開發實現原理圖: 1、Service API對應服務接口。 HelloService.java代碼如下: package com.shan.rpc.service

怎麼理解程式碼實現資料結構

怎麼能更好的用程式碼實現資料結構內容? 個人理解為:1.首先要掌握資料結構的邏輯,也就是說要知道資料結構是怎麼實現的。這一點達不到的話,後面根本就無從談起。                   &nbs