音视频开发21 FFmpeg 视频解复用练习,将一个mp4文件,分解成一个aac文件 和 h264文件

源码:

/**
  目的是将一个mp4文件,通过ffmpeg 解封装 成 aac file 和 h264
*/

#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavutil/log.h"
#include "libavformat/avformat.h"
#include "libavcodec/bsf.h"

#define ERROR_STRING_SIZE 1024

#define ADTS_HEADER_LEN  7;

const int sampling_frequencies[] = {
    96000,  // 0x0
    88200,  // 0x1
    64000,  // 0x2
    48000,  // 0x3
    44100,  // 0x4
    32000,  // 0x5
    24000,  // 0x6
    22050,  // 0x7
    16000,  // 0x8
    12000,  // 0x9
    11025,  // 0xa
    8000   // 0xb
    // 0xc d e f是保留的
};

int adts_header(char * const p_adts_header, const int data_length,
                const int profile, const int samplerate,
                const int channels)
{

    int sampling_frequency_index = 3; // 默认使用48000hz
    int adtsLen = data_length + 7;

    int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]);
    int i = 0;
    for(i = 0; i < frequencies_size; i++)
    {
        if(sampling_frequencies[i] == samplerate)
        {
            sampling_frequency_index = i;
            break;
        }
    }
    if(i >= frequencies_size)
    {
        printf("unsupport samplerate:%d\n", samplerate);
        return -1;
    }

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

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

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

    p_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bits
    p_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bits
    p_adts_header[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bits
    p_adts_header[6] = 0xfc;      //‭11111100‬       //buffer fullness:0x7ff 低6bits
    // number_of_raw_data_blocks_in_frame:
    //    表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。

    return 0;
}

int main(int argc,char **argv)
{
    int ret =0;
    char errors[ERROR_STRING_SIZE+1];  // 主要是用来缓存解析FFmpeg api返回值的错误string
    printf("Hello World! argc = %d\n",argc);

// 在项目debug 中填入如下三个参数,   2_audio_track_5s.mp4 2_audio_track_5s.h264 2_audio_track_5s.aac
    //2_audio_track_5s.mp4放置路径为 D:\AllInformation\qtworkspacenew\build-07-08-homework_demuxermp4file-Desktop_Qt_5_14_2_MinGW_64_bit-Debug
    if(argc != 4){
        printf("params num != 4.  the second param should be xxx.mp4,the third param should be xxx.h264,the last param should be xxx.aac");
        ret = -1;
        return ret;
    }
    //0.取出第二个参数,第二个参数作为 infile name
    char *in_filename = argv[1];

    //0.取出第3个参数,第3个参数作为 outfile.h264 name
    char *h264_filename = argv[2];
    FILE *h264_fd = NULL;

    //0.取出第4个参数,第4个参数作为 outfile.aac name
    char *aac_filename = argv[3];
    FILE *aac_fd = NULL;

    h264_fd = fopen(h264_filename, "wb");
    if(!h264_fd) {
        printf("fopen %s failed\n", h264_filename);
        return -1;
    }

    aac_fd = fopen(aac_filename, "wb");
    if(!aac_fd) {
        printf("fopen %s failed\n", aac_filename);
        return -1;
    }

    //5 最佳视频流,
    int video_index = -1;

    //6. 最佳音频流
    int audio_index = -1;

    // 7.1 找到 h264_mp4toannexb 的过滤器,audio video bit 流过滤器
    const AVBitStreamFilter *bsfilter = NULL;

    // 7.2 过滤器上下文
    AVBSFContext *bsf_ctx = NULL;

    // 8 下来就是要通过 av_read_frame 读取数据dao AVPacket 中了
    AVPacket *avpacket = NULL;

    //1. 分配解复用器上下文
    AVFormatContext * avformatcontext = avformat_alloc_context();
    if(!avformatcontext){
        ret =-2;
        printf("avformatcontext = null because avformat_alloc_context func error goto end\n");
        goto end;
    }

    //2.打开媒体文件并获取媒体文件信息的函数
    ret = avformat_open_input(&avformatcontext,in_filename,NULL,NULL);
    if(ret !=0){
        printf(" avformat_open_input func error goto end\n");
        goto end;
    }

    //3.读取文件的部分信息以获得码流信息
    ret = avformat_find_stream_info(avformatcontext,NULL);
    if(ret <0){
        printf(" avformat_find_stream_info func error goto end\n");
        goto end;
    }


    //4.打印信息
    printf_s("\n==== av_dump_format mp4infilename:%s ===\n", in_filename);
    av_dump_format(avformatcontext, 0, in_filename, 0);
    printf_s("\n==== av_dump_format finish =======\n\n");


    //5.找到最佳视频流
    video_index = av_find_best_stream(avformatcontext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if(video_index < 0 ) {
        printf("av_find_best_stream video_index failed\n");
        ret = video_index;
        goto end;
    }

    //6.找到最佳音频流
    audio_index = av_find_best_stream(avformatcontext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if(audio_index < 0) {
        printf("av_find_best_stream audio_index failed\n");
        ret = audio_index;
        goto end;
    }

    //7.这时候理论上要进行 读取数据了,但是在这之前,因为我们要将从mp4取出来的h264数据存储成ffpaly可以播放的h264格式的,
    //但是一般的mp4格式的文件 并不是 AnnexB格式 的,一般的mp4文件是 AVCC 格式的,因此 要先来搞定可以让ffplay 播放的 AnnexB格式的h264文件

    // 7.1 找到 h264_mp4toannexb 的过滤器,audio video bit 流过滤器
    bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
    if(!bsfilter) {

        printf("av_bsf_get_by_name h264_mp4toannexb failed\n");
        ret = -1;
        goto end;
    }


    // 7.3 将过滤器和过滤器上下文绑定
    ret = av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
    if(ret != 0) {
        printf("av_bsf_alloc failed\n");
        goto end;
    }
//    该av_bsf_alloc方法的说明如下:
//     * Allocate a context for a given bitstream filter. The caller must fill in the
//     * context parameters as described in the documentation and then call
//     * av_bsf_init() before sending any data to the filter.

    // 7.4 将要解复用video的 AVCodecParameters 赋值给 过滤器上下文的AVCodecParameters
    ret = avcodec_parameters_copy(bsf_ctx->par_in, avformatcontext->streams[video_index]->codecpar);
    if(ret < 0) {
        printf("avcodec_parameters_copy failed\n");
        goto end;
    }

    // 7.5 Prepare the filter for use, after all the parameters and options have been set.
    ret = av_bsf_init(bsf_ctx);
    if(ret < 0) {
        av_strerror(ret, errors, ERROR_STRING_SIZE);
        printf("av_bsf_init failed:%s\n", errors);
        goto end;
    }


    // 8 下来就是要通过 av_read_frame 读取数据dao AVPacket 中了
    avpacket = av_packet_alloc();
    if(!avpacket){
        ret = -6;
        printf("av_packet_alloc failed:%s\n", errors);
        goto end;
    }

    while(1){
        //8.1 读取数据到 avpacket 中,
        ret = av_read_frame(avformatcontext,avpacket);
        if(ret < 0 ){// <0 on error or end of file
            av_strerror(ret, errors, ERROR_STRING_SIZE);
            printf("av_read_frame failed:%s\n", errors);
            break;
        }
        // av_read_frame 成功读取到packet,则外部需要进行buf释放
        if(avpacket->stream_index == video_index) {
            //8.2读取到的数据是视频数据
            // 8.2.1 通过 av_bsf_send_packet 处理视频
            ret = av_bsf_send_packet(bsf_ctx, avpacket); // 内部把我们传入的buf转移到自己bsf内部
            if(ret < 0) {       // 基本不会进入该逻辑
                av_strerror(ret, errors, ERROR_STRING_SIZE);
                printf("av_bsf_send_packet failed:%s\n", errors);
                av_packet_unref(avpacket);
                continue;
            }
            while (1) {
                //注意这里,个人认为,这里不应该再次使用 avpacket,参考 av_bsf_receive_packet的api 说明,但是观察老师的代码这块确实还是用了avpakcet
                ret = av_bsf_receive_packet(bsf_ctx, avpacket);
                if(ret != 0) {
                    break;
                }
                size_t size = fwrite(avpacket->data, 1, avpacket->size, h264_fd);
                if(size != avpacket->size)
                {
                    av_log(NULL, AV_LOG_DEBUG, "h264 warning, length of writed data isn't equal pkt->size(%d, %d)\n",
                           size,
                           avpacket->size);
                }
                av_packet_unref(avpacket);
            }
        } else if(avpacket->stream_index == audio_index){
            // 处理音频,从mp4文件中读取到的aac就只有 aac data 的部分,没有头的部分,头的部分我们需要自己添加,使用的方法为自定义的adts_header方法
            char adts_header_buf[7] = {0};
            adts_header(adts_header_buf, avpacket->size,
                        avformatcontext->streams[audio_index]->codecpar->profile,
                        avformatcontext->streams[audio_index]->codecpar->sample_rate,
                        avformatcontext->streams[audio_index]->codecpar->channels);
            fwrite(adts_header_buf, 1, 7, aac_fd);  // 写adts header , ts流不适用,ts流分离出来的packet带了adts header
            size_t size = fwrite( avpacket->data, 1, avpacket->size, aac_fd);   // 写adts data
            if(size != avpacket->size)
            {
                av_log(NULL, AV_LOG_DEBUG, "aac warning, length of writed data isn't equal pkt->size(%d, %d)\n",
                       size,
                       avpacket->size);
            }
            av_packet_unref(avpacket);
        } else {
            av_packet_unref(avpacket);
        }
    }

    end:
    av_packet_free(&avpacket);
    av_bsf_free(&bsf_ctx);
    avformat_close_input(&avformatcontext);
    avformat_free_context(avformatcontext);
    return ret;
}

测试播放

对于 aac 和 h264 都可以使用 ffplay 命令直接播放
ffplay 2_audio_track_5s.aac
ffplay 2_audio_track_5s.h264
ffplay 2_audio_track_5s.mp4


对于 pcm 数据

ffplay -ac 2 -ar 48000 -f s16le 48000_2_s16le.pcm
ffplay -ac 2 -ar 48000 -f f32le 48000_2_f32le.pcm


对于 YUV 数据

ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25  source.200kbps.768x320_10s.yuv


aac 和 h264 之所以能不添加其他的参数就能正确的播放,是因为 aac 的每一帧都有头部信息, 在头部信息中,就有声音的这些参数。
h264 文件的的每一个GOP,都会有PPS和SPS发送,这里面就包含了 视频播放的三要素

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用ffmpeg复用TS流,可以使用以下命令: ``` ffmpeg -i input.ts -map 0 -c copy output.mp4 ``` 其中,`input.ts`是输入的TS流文件,`output.mp4`是输出的目标文件。这个命令会将输入的TS流复用并复制到目标文件中。 释一下命令的参数: - `-i input.ts`指定输入文件为`input.ts`,即要复用的TS流文件。 - `-map 0`表示将输入文件中的所有流都映射到输出文件中。 - `-c copy`表示使用原始的编码格式进行复制,不进行任何重新编码操作。这样可以保持原始的编码质量和格式。 - `output.mp4`是输出的目标文件名,可以根据需要自行指定。 这个命令会将TS流中的所有流(包括视频流、音频流等)复用,并将它们复制到输出文件中,不对流进行任何重新编码操作。这样可以快速地提取TS流中的音视频数据。 注意:这只是一个简单的示例命令,具体的使用方法和参数根据实际情况可能会有所不同。在实际使用中,可以根据需要调整命令参数来满足自己的需求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [修改ffmpeg源码,并用它对多路节目TS流复用及播放](https://blog.csdn.net/cf125313/article/details/80764829)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [基于FFmpeg视频播放器之二:复用](https://blog.csdn.net/caoshangpa/article/details/124486435)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [FLV复用代码实现!!!!](https://download.csdn.net/download/qq_39466755/87020098)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值