FFmpeg多媒體文件處理

  • FFmpeg日誌的使用及目錄操作
  • FFmpeg的基本概念及常用結構體
  • 對複用 / 解複用及流操作的各種實戰

FFmpeg代碼結構

libavcodec 提供了一系列編碼器的實現
libavformat 實現在流協議,容器格式及其本IO訪問
libavutil 包括hash器,解碼器和各種工具函數
libavfilter 提供了各種音視頻過濾器
libavdevice 提供了訪問捕獲設備和回放設備的接口
libswresample 實現了混音和重採樣
libswscale 實現了色彩轉換和縮放工具

FFmpeg日誌系統

include <libavutil/log.h>

av_log_set_level(AV_LOG_DEBUG)

av_log(NULL,AV_LOG_INFO,"...%s\n",op)

常用日誌級別

  • AV_LOG_ERROR //級別最高
  • AV_LOG_WARNING
  • AV_LOG_INFO
  • AV_LOG_DEBUG

示例:

 vi ffmpeg_log.c
#include<stdio.h>
#include<libavutil/log.h>

int main(int argc,char* argv[])
{
        av_log_set_level(AV_LOG_DEBUG);

        av_log(NULL,AV_LOG_INFO,"hello world!%s\n","abc");

        return 0;
}
clang -g -o ffmpeg_log ffmpeg_log.c -lavutil
./ffmpeg_log

打印:

hello world!abc

FFmpeg文件與目錄操作

文件的刪除與重命名

  • avpriv_io_delete()
  • avpriv_io_move()
vi ffmpeg_file.c
#include<libavformat/avformat.h>
  
int main(int argc,char* argv[])
{
        int ret;

        ret = avpriv_io_move("11.txt","22.txt");
        if(ret < 0){
                av_log(NULL,AV_LOG_ERROR,"failed to rename\n");
                return -1;
        }
        av_log(NULL,AV_LOG_INFO,"success to rename\n");

        ret = avpriv_io_delete("./mytestfile.txt");
        if(ret < 0 ){
                av_log(NULL,AV_LOG_ERROR,"delete failed\n");
                return -1;
        }       
        av_log(NULL,AV_LOG_INFO,"success to delete\n");
        

        return 0;
}
clang -g -o ffmpeg_file ffmpeg_file.c `pkg-config --libs libavformat`
./ffmpeg_file

打印:

success to rename
success to delete

操作目錄重要函數

  • avio_open_dir()

  • avio_read_dir()

  • avio_close_dir()

  • AVIODirContext 操作目錄的上下文

  • AVIODirEntry 目錄項。用於存放文件名,文件大小等信息。

實現簡單的ls命令

ls -alt
#include<libavutil/log.h>
#include<libavformat/avformat.h>

int main(int argc,char* argv[])
{

        int ret;
        AVIODirContext *ctx = NULL;
        AVIODirEntry *entry = NULL;

        av_log_set_level(AV_LOG_INFO);

        ret = avio_open_dir(&ctx,"./",NULL);
        if(ret < 0){
                av_log(NULL,AV_LOG_ERROR,"can not open dir:%s\n",av_err2str(ret));

                return -1;
        }

        while(1){
                ret = avio_read_dir(ctx,&entry);

                if(ret < 0){
                        av_log(NULL,AV_LOG_ERROR,"can not read dir:%s\n",av_err2str(ret));

                        goto __fail;
                }

                if(!entry){
                        break;
                }

                av_log(NULL,AV_LOG_INFO,"%12"PRId64 " %s \n",entry->size,entry->name);

                avio_free_directory_entry(&entry);
        }


__fail:
        avio_close_dir(&ctx);

        return 0;

}
clang -g -o mylist ffmpeg_list.c `pkg-config --libs libavformat libavutil`
./mylist #輸出文件列表

FFmpeg處理流數據的基本概念

  • 多媒體文件其實是個容器
  • 在容器裏有很多流(Stream/Track)
  • 每種流是由不同的編碼器編碼的
  • 從流中讀出的數據稱爲包
  • 在一個包中包含着一個或多個幀

幾個重要的結構體

  • AVFormatContext
  • AVStream
  • AVPacket

FFmpeg操作流數據的基本概念

解複用–>獲取流–>讀數據包–>釋放資源

FFmpeg打印音視頻Meta信息

  • av_register_all()
  • avformat_open_input()/avformat_close_input
  • av_dump_format()
vi mediainfo.c
#include<libavutil/log.h>
#include<libavformat/avformat.h>

int main(int argc,char* argv[])
{

        int ret;

        AVFormatContext *fmt_ctx = NULL;

        av_log_set_level(AV_LOG_INFO);

        av_register_all();

        ret = avformat_open_input(&fmt_ctx,"./test.mp4",NULL,NULL);
        if(ret<0){
                av_log(NULL,AV_LOG_ERROR,"can not open file:%s\n",av_err2str(ret));

                return -1;
        }

        av_dump_format(fmt_ctx,0,"./test.mp4",0);

        avformat_close_input(&fmt_ctx);


        return 0;
}
 clang -g -o mediainfo mediainfo.c `pkg-config --libs libavutil libavformat`
./mediainfo

在這裏插入圖片描述

FFmpeg抽取音頻數據

  • av_init_packet()
  • av_find_best_stream()
  • av_read_frame()/av_packet_unref()
vi exrta_audio.c
#include<stdio.h>
#include<libavutil/log.h>
#include<libavformat/avformat.h>

#define ADTS_HEADER_LEN  7;

void adts_header(char *szAdtsHeader, int dataLen){

    int audio_object_type = 2;
    int sampling_frequency_index = 7;
    int channel_config = 2;

    int adtsLen = dataLen + 7;

    szAdtsHeader[0] = 0xff;         //syncword:0xfff                          高8bits
    szAdtsHeader[1] = 0xf0;         //syncword:0xfff                          低4bits
    szAdtsHeader[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bit
    szAdtsHeader[1] |= (0 << 1);    //Layer:0                                 2bits 
    szAdtsHeader[1] |= 1;           //protection absent:1                     1bit

    szAdtsHeader[2] = (audio_object_type - 1)<<6;            //profile:audio_object_type - 1                      2bits
    szAdtsHeader[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index  4bits 
    szAdtsHeader[2] |= (0 << 1);                             //private bit:0                                      1bit
    szAdtsHeader[2] |= (channel_config & 0x04)>>2;           //channel configuration:channel_config               高1bit

    szAdtsHeader[3] = (channel_config & 0x03)<<6;     //channel configuration:channel_config      低2bits
    szAdtsHeader[3] |= (0 << 5);                      //original:0                               1bit
    szAdtsHeader[3] |= (0 << 4);                      //home:0                                   1bit
    szAdtsHeader[3] |= (0 << 3);                      //copyright id bit:0                       1bit  
    szAdtsHeader[3] |= (0 << 2);                      //copyright id start:0                     1bit
    szAdtsHeader[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bits

    szAdtsHeader[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中間8bits
    szAdtsHeader[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bits
    szAdtsHeader[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bits
    szAdtsHeader[6] = 0xfc;
}

int main(int argc,char* argv[])
{

	int ret;
	int len;
	int audio_index;
	char* src = NULL;
	char* dst = NULL;
	
	AVPacket pkt;
	AVFormatContext *fmt_ctx = NULL;	

	av_log_set_level(AV_LOG_INFO);
	
	av_register_all();

	//1 read two params from console
	if(argc < 3){
		av_log(NULL,AV_LOG_ERROR,"this count of params should be more than 3 \n");
		return -1;
	}

	src = argv[1];
	dst = argv[2];
	if(!src || !dst){
		av_log(NULL,AV_LOG_ERROR,"src or dst is null! \n");
	}
	
	ret = avformat_open_input(&fmt_ctx,src,NULL,NULL);
	if(ret<0){
		av_log(NULL,AV_LOG_ERROR,"can not open file:%s\n",av_err2str(ret));
		return -1;
	}

	FILE* dst_fd = fopen(dst,"wb");
	if(!dst_fd){
                av_log(NULL,AV_LOG_ERROR,"can not open out file! \n");
		avformat_close_input(&fmt_ctx);
                return -1;
        }
	
	av_dump_format(fmt_ctx,0,src,0);

	//2 get stream
	ret = av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
	if(ret<0){
                av_log(NULL,AV_LOG_ERROR,"can not find best stream! \n");
                avformat_close_input(&fmt_ctx);
		fclose(dst_fd);
		return -1;
        }
	audio_index = ret;

	av_init_packet(&pkt);
	
	while(av_read_frame(fmt_ctx,&pkt) >= 0){
		if(pkt.stream_index == audio_index){
			char adts_header_buf[7];
			adts_header(adts_header_buf,pkt.size);
			fwrite(adts_header_buf,1,7,dst_fd);
			//3 write audio data to aac file.

			len = fwrite(pkt.data,1,pkt.size,dst_fd);
			if(len != pkt.size){
				 av_log(NULL,AV_LOG_WARNING,"waning,length of data is not equal size of pkt! \n");
			}
		}

		av_packet_unref(&pkt);
	}	 
	
	avformat_close_input(&fmt_ctx);
	if(dst_fd){
		fclose(dst_fd);
	}

	return 0;
}

clang -g -o extra_audio extra_audio.c  `pkg-config --libs libavutil libavformat`
./extra_audio /Volumes/SoftCode/test/test02/test.mp4  ./test.aac

在這裏插入圖片描述

 ffplay /Volumes/SoftCode/test/test02/test.aac 

FFmpeg抽取視頻H264數據

  • Start code
  • SPS/PPS
  • codec->extradata

FFmpeg將mp4轉成flv

  • avformat_alloc_output_context2() / avformat_free_context()
  • avformat_new_stream()
  • avcodec_parameters_copy()
  • avformat_write_header()
  • avformat_write_frame()/av_interleaved_write_frame()
  • av_write_trailer()
vi remuxing.c
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>

static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;

    printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           tag,
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
           pkt->stream_index);
}

int main(int argc, char **argv)
{
    AVOutputFormat *ofmt = NULL;
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt;
    const char *in_filename, *out_filename;
    int ret, i;
    int stream_index = 0;
    int *stream_mapping = NULL;
    int stream_mapping_size = 0;

    if (argc < 3) {
        printf("usage: %s input output\n"
               "API example program to remux a media file with libavformat and libavcodec.\n"
               "The output format is guessed according to the file extension.\n"
               "\n", argv[0]);
        return 1;
    }

    in_filename  = argv[1];
    out_filename = argv[2];

    av_register_all();

    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        fprintf(stderr, "Could not open input file '%s'", in_filename);
        goto end;
    }

    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        fprintf(stderr, "Failed to retrieve input stream information");
        goto end;
    }

    av_dump_format(ifmt_ctx, 0, in_filename, 0);

    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
    if (!ofmt_ctx) {
        fprintf(stderr, "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }

    stream_mapping_size = ifmt_ctx->nb_streams;
    stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));
    if (!stream_mapping) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    ofmt = ofmt_ctx->oformat;

    for (i = 0; i < ifmt_ctx->nb_streams; i++) {
        AVStream *out_stream;
        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) {
            stream_mapping[i] = -1;
            continue;
        }

        stream_mapping[i] = stream_index++;

        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream) {
            fprintf(stderr, "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }

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

    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Could not open output file '%s'", out_filename);
            goto end;
        }
    }

    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error occurred when opening output file\n");
        goto end;
    }

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

        ret = av_read_frame(ifmt_ctx, &pkt);
        if (ret < 0)
            break;

        in_stream  = ifmt_ctx->streams[pkt.stream_index];
        if (pkt.stream_index >= stream_mapping_size ||
            stream_mapping[pkt.stream_index] < 0) {
            av_packet_unref(&pkt);
            continue;
        }

        pkt.stream_index = stream_mapping[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];
        log_packet(ifmt_ctx, &pkt, "in");

        /* copy packet */
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        log_packet(ofmt_ctx, &pkt, "out");

        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            fprintf(stderr, "Error muxing packet\n");
            break;
        }
        av_packet_unref(&pkt);
    }

    av_write_trailer(ofmt_ctx);
end:

    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) {
        fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
        return 1;
    }

    return 0;
}

 clang -g -o remuxing  remuxing.c  `pkg-config --libs libavutil libavformat`
./remuxing /Volumes/SoftCode/yinship_test/test03/test.mp4 test.flv

在這裏插入圖片描述

 ffplay test.flv 

在這裏插入圖片描述

FFmpeg音視頻裁剪

  • av_seek_frame()
vi cutvideo.c
#include <stdlib.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>

static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;

    printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           tag,
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
           pkt->stream_index);
}

int cut_video(double from_seconds, double end_seconds, const char* in_filename, const char* out_filename) {
    AVOutputFormat *ofmt = NULL;
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt;
    int ret, i;

    av_register_all();

    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        fprintf(stderr, "Could not open input file '%s'", in_filename);
        goto end;
    }

    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        fprintf(stderr, "Failed to retrieve input stream information");
        goto end;
    }

    av_dump_format(ifmt_ctx, 0, in_filename, 0);

    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
    if (!ofmt_ctx) {
        fprintf(stderr, "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }

    ofmt = ofmt_ctx->oformat;

    for (i = 0; i < ifmt_ctx->nb_streams; i++) {
        AVStream *in_stream = ifmt_ctx->streams[i];
        AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
        if (!out_stream) {
            fprintf(stderr, "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }

        ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
        if (ret < 0) {
            fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
            goto end;
        }
        out_stream->codec->codec_tag = 0;
        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }
    av_dump_format(ofmt_ctx, 0, out_filename, 1);

    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Could not open output file '%s'", out_filename);
            goto end;
        }
    }

    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error occurred when opening output file\n");
        goto end;
    }

    //    int indexs[8] = {0};


    //    int64_t start_from = 8*AV_TIME_BASE;
    ret = av_seek_frame(ifmt_ctx, -1, from_seconds*AV_TIME_BASE, AVSEEK_FLAG_ANY);
    if (ret < 0) {
        fprintf(stderr, "Error seek\n");
        goto end;
    }

    int64_t *dts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
    memset(dts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);
    int64_t *pts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
    memset(pts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);

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

        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];

        log_packet(ifmt_ctx, &pkt, "in");

        if (av_q2d(in_stream->time_base) * pkt.pts > end_seconds) {
            av_free_packet(&pkt);
            break;
        }

        if (dts_start_from[pkt.stream_index] == 0) {
            dts_start_from[pkt.stream_index] = pkt.dts;
            printf("dts_start_from: %s\n", av_ts2str(dts_start_from[pkt.stream_index]));
        }
        if (pts_start_from[pkt.stream_index] == 0) {
            pts_start_from[pkt.stream_index] = pkt.pts;
            printf("pts_start_from: %s\n", av_ts2str(pts_start_from[pkt.stream_index]));
        }

        /* copy packet */
        pkt.pts = av_rescale_q_rnd(pkt.pts - pts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        pkt.dts = av_rescale_q_rnd(pkt.dts - dts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        if (pkt.pts < 0) {
            pkt.pts = 0;
        }
        if (pkt.dts < 0) {
            pkt.dts = 0;
        }
        pkt.duration = (int)av_rescale_q((int64_t)pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        log_packet(ofmt_ctx, &pkt, "out");
        printf("\n");

        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            fprintf(stderr, "Error muxing packet\n");
            break;
        }
        av_free_packet(&pkt);
    }
    free(dts_start_from);
    free(pts_start_from);

    av_write_trailer(ofmt_ctx);
end:

    avformat_close_input(&ifmt_ctx);

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

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

    return 0;
}

int main(int argc, char *argv[]){
    if(argc < 5){
        fprintf(stderr, "Usage: \
                command startime, endtime, srcfile, outfile");
        return -1;
    }

    double startime = atoi(argv[1]);
    double endtime = atoi(argv[2]);
    cut_video(startime, endtime, argv[3], argv[4]);

    return 0;
}


clang -g -o cutvideo cutvideo.c  `pkg-config --libs libavutil libavformat libavcodec`
./cutvideo  10  100  /Volumes/SoftCode/yinship_test/test03/test.mp4  testcut.mp4
ffplay testcut.mp4
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章