zoukankan      html  css  js  c++  java
  • ffmpeg文件生成m3u8文件及ts切片程序(一)

    ffmpeg文件生成m3u8文件及ts切片程序(一)

    实现目标:输入本地文件,实现m3u8切片,功能点请看注释,注意:注释很重要。

    参考:

    http://www.cnblogs.com/mystory/archive/2013/04/07/3006200.html

    https://github.com/johnf/m3u8-segmenter/pull/10/files#diff-e1c7f1b21ff66b32c10d790c3855aedeR42

    https://github.com/johnf/m3u8-segmenter


    //ffmpeg.h
    #ifndef __FFMPEG_H__
    #define __FFMPEG_H__

    #include "info.h"

    extern "C"
    {
    #include "libavformat/avformat.h"
    #include "libavformat/avio.h"
    #include "libavcodec/avcodec.h"
    #include "libswscale/swscale.h"
    #include "libavutil/avutil.h"
    #include "libavutil/mathematics.h"
    #include "libswresample/swresample.h"
    #include "libavutil/opt.h"
    #include "libavutil/channel_layout.h"
    #include "libavutil/samplefmt.h"
    #include "libavdevice/avdevice.h" //摄像头所用
    #include "libavfilter/avfilter.h"
    #include "libavutil/error.h"
    #include "libavutil/mathematics.h"
    #include "libavutil/time.h"
    #include "libavutil/fifo.h"
    #include "libavutil/audio_fifo.h" //这里是做分片时候重采样编码音频用的
    #include "inttypes.h"
    #include "stdint.h"
    };

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

    #define AUDIO_ID 0 //packet 中的ID ,如果先加入音频 pocket 则音频是 0 视频是1,否则相反(影响add_out_stream顺序)
    #define VIDEO_ID 1

    //#define INPUTURL "../in_stream/22.ts"
    #define INPUTURL "../in_stream/avier1.mp4"
    //#define INPUTURL "../in_stream/father.avi" //这个有问题?没有音频
    //#define INPUTURL "../in_stream/ceshi.avi"

    //m3u8 param
    #define OUTPUT_PREFIX "ZWG_TEST" //切割文件的前缀
    #define M3U8_FILE_NAME "ZWG_TEST.m3u8" //生成的m3u8文件名
    #define URL_PREFIX "../out_stream/" //生成目录
    #define NUM_SEGMENTS 50 //在磁盘上一共最多存储多少个分片
    #define SEGMENT_DURATION 10 //每一片切割多少秒
    extern unsigned int m_output_index; //生成的切片文件顺序编号(第几个文件)
    extern char m_output_file_name[256]; //输入的要切片的文件


    extern int nRet; //状态标志
    extern AVFormatContext* icodec; //输入流context
    extern AVFormatContext* ocodec ; //输出流context
    extern char szError[256]; //错误字符串
    extern AVStream* ovideo_st;
    extern AVStream* oaudio_st;
    extern int video_stream_idx;
    extern int audio_stream_idx;
    extern AVCodec *audio_codec;
    extern AVCodec *video_codec;
    extern AVBitStreamFilterContext * vbsf_aac_adtstoasc; //aac->adts to asc过滤器
    static struct SwsContext * img_convert_ctx_video = NULL;
    static int sws_flags = SWS_BICUBIC; //差值算法,双三次
    extern AVBitStreamFilterContext * vbsf_h264_toannexb;
    extern int IsAACCodes;

    int init_demux(char * Filename,AVFormatContext ** iframe_c);
    int init_mux();
    int uinit_demux();
    int uinit_mux();
    //for mux
    AVStream * add_out_stream(AVFormatContext* output_format_context,AVMediaType codec_type_t);

    //具体的切片程序
    void slice_up();
    //填写m3u8文件
    int write_index_file(const unsigned int first_segment, const unsigned int last_segment, const int end, const unsigned int actual_segment_durations[]);


    #endif

    //ffmpeg.cpp
    #include "ffmpeg.h"

    int nRet = 0;
    AVFormatContext* icodec = NULL;
    AVFormatContext* ocodec = NULL;
    char szError[256];
    AVStream * ovideo_st = NULL;
    AVStream * oaudio_st = NULL;
    int video_stream_idx = -1;
    int audio_stream_idx = -1;
    AVCodec *audio_codec = NULL;
    AVCodec *video_codec = NULL;
    AVBitStreamFilterContext * vbsf_aac_adtstoasc = NULL;
    AVBitStreamFilterContext * vbsf_h264_toannexb = NULL;
    int IsAACCodes = 0;

    //m3u8 param
    unsigned int m_output_index = 1; //生成的切片文件顺序编号
    char m_output_file_name[256]; //输入的要切片的文件

    int init_demux(char * Filename,AVFormatContext ** iframe_c)
    {
    int i = 0;
    nRet = avformat_open_input(iframe_c, Filename,NULL, NULL);
    if (nRet != 0)
    {
    av_strerror(nRet, szError, 256);
    printf(szError);
    printf(" ");
    printf("Call avformat_open_input function failed! ");
    return 0;
    }
    if (avformat_find_stream_info(*iframe_c,NULL) < 0)
    {
    printf("Call av_find_stream_info function failed! ");
    return 0;
    }
    //输出视频信息
    av_dump_format(*iframe_c, -1, Filename, 0);

    //添加音频信息到输出context
    for (i = 0; i < (*iframe_c)->nb_streams; i++)
    {
    if ((*iframe_c)->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
    {
    video_stream_idx = i;
    }
    else if ((*iframe_c)->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
    {
    audio_stream_idx = i;
    }
    }

    if ((strstr(icodec->iformat->name, "flv") != NULL) ||
    (strstr(icodec->iformat->name, "mp4") != NULL) ||
    (strstr(icodec->iformat->name, "mov") != NULL))
    {
    if (icodec->streams[video_stream_idx]->codec->codec_id == AV_CODEC_ID_H264) //AV_CODEC_ID_H264
    {
    //这里注意:"h264_mp4toannexb",一定是这个字符串,无论是 flv,mp4,mov格式
    vbsf_h264_toannexb = av_bitstream_filter_init("h264_mp4toannexb");
    }
    if (icodec->streams[audio_stream_idx]->codec->codec_id == AV_CODEC_ID_AAC) //AV_CODEC_ID_AAC
    {
    IsAACCodes = 1;
    }
    }

    return 1;
    }

    int init_mux()
    {
    int ret = 0;
    /* allocate the output media context */
    avformat_alloc_output_context2(&ocodec, NULL,NULL, m_output_file_name);
    if (!ocodec)
    {
    return getchar();
    }
    AVOutputFormat* ofmt = NULL;
    ofmt = ocodec->oformat;

    /* open the output file, if needed */
    if (!(ofmt->flags & AVFMT_NOFILE))
    {
    if (avio_open(&ocodec->pb, m_output_file_name, AVIO_FLAG_WRITE) < 0)
    {
    printf("Could not open '%s' ", m_output_file_name);
    return getchar();
    }
    }

    //这里添加的时候AUDIO_ID/VIDEO_ID有影响
    //添加音频信息到输出context
    if(audio_stream_idx != -1)//如果存在音频
    {
    oaudio_st = add_out_stream(ocodec, AVMEDIA_TYPE_AUDIO);
    }

    //添加视频信息到输出context
    if (video_stream_idx != -1)//如果存在视频
    {
    ovideo_st = add_out_stream(ocodec,AVMEDIA_TYPE_VIDEO);
    }

    av_dump_format(ocodec, 0, m_output_file_name, 1);

    ret = avformat_write_header(ocodec, NULL);
    if (ret != 0)
    {
    printf("Call avformat_write_header function failed. ");
    return 0;
    }
    return 1;
    }

    int uinit_demux()
    {
    /* free the stream */
    av_free(icodec);
    if (vbsf_h264_toannexb !=NULL)
    {
    av_bitstream_filter_close(vbsf_h264_toannexb);
    vbsf_h264_toannexb = NULL;
    }
    return 1;
    }

    int uinit_mux()
    {
    int i = 0;
    nRet = av_write_trailer(ocodec);
    if (nRet < 0)
    {
    av_strerror(nRet, szError, 256);
    printf(szError);
    printf(" ");
    printf("Call av_write_trailer function failed ");
    }
    if (vbsf_aac_adtstoasc !=NULL)
    {
    av_bitstream_filter_close(vbsf_aac_adtstoasc);
    vbsf_aac_adtstoasc = NULL;
    }

    /* Free the streams. */
    for (i = 0; i < ocodec->nb_streams; i++)
    {
    av_freep(&ocodec->streams[i]->codec);
    av_freep(&ocodec->streams[i]);
    }
    if (!(ocodec->oformat->flags & AVFMT_NOFILE))
    {
    /* Close the output file. */
    avio_close(ocodec->pb);
    }
    av_free(ocodec);
    return 1;
    }

    AVStream * add_out_stream(AVFormatContext* output_format_context,AVMediaType codec_type_t)
    {
    AVStream * in_stream = NULL;
    AVStream * output_stream = NULL;
    AVCodecContext* output_codec_context = NULL;

    output_stream = avformat_new_stream(output_format_context,NULL);
    if (!output_stream)
    {
    return NULL;
    }

    switch (codec_type_t)
    {
    case AVMEDIA_TYPE_AUDIO:
    in_stream = icodec->streams[audio_stream_idx];
    break;
    case AVMEDIA_TYPE_VIDEO:
    in_stream = icodec->streams[video_stream_idx];
    break;
    default:
    break;
    }

    output_stream->id = output_format_context->nb_streams - 1;
    output_codec_context = output_stream->codec;
    output_stream->time_base = in_stream->time_base;

    int ret = 0;
    ret = avcodec_copy_context(output_stream->codec, in_stream->codec);
    if (ret < 0)
    {
    printf("Failed to copy context from input to output stream codec context ");
    return NULL;
    }

    //这个很重要,要么纯复用解复用,不做编解码写头会失败,
    //另或者需要编解码如果不这样,生成的文件没有预览图,还有添加下面的header失败,置0之后会重新生成extradata
    output_codec_context->codec_tag = 0;

    //if(! strcmp( output_format_context-> oformat-> name, "mp4" ) ||
    //!strcmp (output_format_context ->oformat ->name , "mov" ) ||
    //!strcmp (output_format_context ->oformat ->name , "3gp" ) ||
    //!strcmp (output_format_context ->oformat ->name , "flv"))
    if(AVFMT_GLOBALHEADER & output_format_context->oformat->flags)
    {
    output_codec_context->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }
    return output_stream;
    }

    int write_index_file(const unsigned int first_segment, const unsigned int last_segment, const int end, const unsigned int actual_segment_durations[])
    {
    FILE *index_fp = NULL;
    char *write_buf = NULL;
    unsigned int i = 0;
    char m3u8_file_pathname[256] = {0};

    sprintf(m3u8_file_pathname,"%s%s",URL_PREFIX,M3U8_FILE_NAME);

    index_fp = fopen(m3u8_file_pathname,"w");
    if (!index_fp)
    {
    printf("Could not open m3u8 index file (%s), no index file will be created ",(char *)m3u8_file_pathname);
    return -1;
    }

    write_buf = (char *)malloc(sizeof(char) * 1024);
    if (!write_buf)
    {
    printf("Could not allocate write buffer for index file, index file will be invalid ");
    fclose(index_fp);
    return -1;
    }


    if (NUM_SEGMENTS)
    {
    //#EXT-X-MEDIA-SEQUENCE:<Number> 播放列表文件中每个媒体文件的URI都有一个唯一的序列号。URI的序列号等于它之前那个RUI的序列号加一(没有填0)
    sprintf(write_buf,"#EXTM3U #EXT-X-TARGETDURATION:%lu #EXT-X-MEDIA-SEQUENCE:%u ",SEGMENT_DURATION,first_segment);
    }
    else
    {
    sprintf(write_buf,"#EXTM3U #EXT-X-TARGETDURATION:%lu ",SEGMENT_DURATION);
    }
    if (fwrite(write_buf, strlen(write_buf), 1, index_fp) != 1)
    {
    printf("Could not write to m3u8 index file, will not continue writing to index file ");
    free(write_buf);
    fclose(index_fp);
    return -1;
    }

    for (i = first_segment; i <= last_segment; i++)
    {
    sprintf(write_buf,"#EXTINF:%u, %s%s-%u.ts ",actual_segment_durations[i-1],URL_PREFIX,OUTPUT_PREFIX,i);
    if (fwrite(write_buf, strlen(write_buf), 1, index_fp) != 1)
    {
    printf("Could not write to m3u8 index file, will not continue writing to index file ");
    free(write_buf);
    fclose(index_fp);
    return -1;
    }
    }

    if (end)
    {
    sprintf(write_buf,"#EXT-X-ENDLIST ");
    if (fwrite(write_buf, strlen(write_buf), 1, index_fp) != 1)
    {
    printf("Could not write last file and endlist tag to m3u8 index file ");
    free(write_buf);
    fclose(index_fp);
    return -1;
    }
    }

    free(write_buf);
    fclose(index_fp);
    return 0;
    }

    void slice_up()
    {
    int write_index = 1;
    unsigned int first_segment = 1; //第一个分片的标号
    unsigned int last_segment = 0; //最后一个分片标号
    int decode_done = 0; //文件是否读取完成
    int remove_file = 0; //是否要移除文件(写在磁盘的分片已经达到最大)
    char remove_filename[256] = {0}; //要从磁盘上删除的文件名称
    double prev_segment_time = 0; //上一个分片时间
    int ret = 0;
    unsigned int actual_segment_durations[1024] = {0}; //各个分片文件实际的长度

    //填写第一个输出文件名称
    sprintf(m_output_file_name,"%s%s-%u.ts",URL_PREFIX,OUTPUT_PREFIX,m_output_index ++);

    //****************************************创建输出文件(写头部)
    init_mux();

    write_index = !write_index_file(first_segment, last_segment, 0, actual_segment_durations);

    do
    {
    unsigned int current_segment_duration;
    double segment_time = prev_segment_time;
    AVPacket packet;
    av_init_packet(&packet);

    decode_done = av_read_frame(icodec, &packet);
    if (decode_done < 0)
    {
    break;
    }

    if (av_dup_packet(&packet) < 0)
    {
    printf("Could not duplicate packet");
    av_free_packet(&packet);
    break;
    }

    if (packet.stream_index == video_stream_idx )
    {
    segment_time = packet.pts * av_q2d(icodec->streams[video_stream_idx]->time_base);
    }
    else if (video_stream_idx < 0)
    {
    segment_time = packet.pts * av_q2d(icodec->streams[audio_stream_idx]->time_base);
    }
    else
    {
    segment_time = prev_segment_time;
    }

    //这里是为了纠错,有文件pts为不可用值
    if (packet.pts < packet.dts)
    {
    packet.pts = packet.dts;
    }

    //视频
    if (packet.stream_index == video_stream_idx )
    {
    if (vbsf_h264_toannexb != NULL)
    {
    AVPacket filteredPacket = packet;
    int a = av_bitstream_filter_filter(vbsf_h264_toannexb,
    ovideo_st->codec, NULL,&filteredPacket.data, &filteredPacket.size, packet.data, packet.size, packet.flags & AV_PKT_FLAG_KEY);
    if (a > 0)
    {
    av_free_packet(&packet);
    packet.pts = filteredPacket.pts;
    packet.dts = filteredPacket.dts;
    packet.duration = filteredPacket.duration;
    packet.flags = filteredPacket.flags;
    packet.stream_index = filteredPacket.stream_index;
    packet.data = filteredPacket.data;
    packet.size = filteredPacket.size;
    }
    else if (a < 0)
    {
    fprintf(stderr, "%s failed for stream %d, codec %s",
    vbsf_h264_toannexb->filter->name,packet.stream_index,ovideo_st->codec->codec ? ovideo_st->codec->codec->name : "copy");
    av_free_packet(&packet);
    getchar();
    }
    }

    packet.pts = av_rescale_q_rnd(packet.pts, icodec->streams[video_stream_idx]->time_base, ovideo_st->time_base, AV_ROUND_NEAR_INF);
    packet.dts = av_rescale_q_rnd(packet.dts, icodec->streams[video_stream_idx]->time_base, ovideo_st->time_base, AV_ROUND_NEAR_INF);
    packet.duration = av_rescale_q(packet.duration,icodec->streams[video_stream_idx]->time_base, ovideo_st->time_base);

    packet.stream_index = VIDEO_ID; //这里add_out_stream顺序有影响
    printf("video ");
    }
    else if (packet.stream_index == audio_stream_idx)
    {
    packet.pts = av_rescale_q_rnd(packet.pts, icodec->streams[audio_stream_idx]->time_base, oaudio_st->time_base, AV_ROUND_NEAR_INF);
    packet.dts = av_rescale_q_rnd(packet.dts, icodec->streams[audio_stream_idx]->time_base, oaudio_st->time_base, AV_ROUND_NEAR_INF);
    packet.duration = av_rescale_q(packet.duration,icodec->streams[audio_stream_idx]->time_base, oaudio_st->time_base);

    packet.stream_index = AUDIO_ID; //这里add_out_stream顺序有影响
    printf("audio ");
    }

    current_segment_duration = (int)(segment_time - prev_segment_time + 0.5);
    actual_segment_durations[last_segment] = (current_segment_duration > 0 ? current_segment_duration: 1);

    if (segment_time - prev_segment_time >= SEGMENT_DURATION)
    {
    ret = av_write_trailer(ocodec); // close ts file and free memory
    if (ret < 0)
    {
    printf("Warning: Could not av_write_trailer of stream ");
    }

    avio_flush(ocodec->pb);
    avio_close(ocodec->pb);

    if (NUM_SEGMENTS && (int)(last_segment - first_segment) >= NUM_SEGMENTS - 1)
    {
    remove_file = 1;
    first_segment++;
    }
    else
    {
    remove_file = 0;
    }

    if (write_index)
    {
    write_index = !write_index_file(first_segment, ++last_segment, 0,actual_segment_durations);
    }

    if (remove_file)
    {
    sprintf(remove_filename,"%s%s-%u.ts",URL_PREFIX,OUTPUT_PREFIX,first_segment - 1);
    remove(remove_filename);
    }

    sprintf(m_output_file_name,"%s%s-%u.ts",URL_PREFIX,OUTPUT_PREFIX,m_output_index ++);
    if (avio_open(&ocodec->pb, m_output_file_name, AVIO_FLAG_WRITE) < 0)
    {
    printf("Could not open '%s' ", m_output_file_name);
    break;
    }

    // Write a new header at the start of each file
    if (avformat_write_header(ocodec, NULL))
    {
    printf("Could not write mpegts header to first output file ");
    exit(1);
    }

    prev_segment_time = segment_time;
    }

    ret = av_interleaved_write_frame(ocodec, &packet);
    if (ret < 0)
    {
    printf("Warning: Could not write frame of stream ");
    }
    else if (ret > 0)
    {
    printf("End of stream requested ");
    av_free_packet(&packet);
    break;
    }

    av_free_packet(&packet);
    } while (!decode_done);

    //****************************************完成输出文件(写尾部)
    uinit_mux();

    if (NUM_SEGMENTS && (int)(last_segment - first_segment) >= NUM_SEGMENTS - 1)
    {
    remove_file = 1;
    first_segment++;
    }
    else
    {
    remove_file = 0;
    }

    if (write_index)
    {
    write_index_file(first_segment, ++last_segment, 1, actual_segment_durations);
    }

    if (remove_file)
    {
    sprintf(remove_filename,"%s%s-%u.ts",URL_PREFIX,OUTPUT_PREFIX,first_segment - 1);
    remove(remove_filename);
    }

    return;
    }

    实现效果:

    源码地址:http://download.csdn.net/detail/zhuweigangzwg/9456780


    交流请加QQ群:62054820
    QQ:379969650.

    ---------------------
    作者:zwg流泪
    来源:CSDN
    原文:https://blog.csdn.net/zhuweigangzwg/article/details/50837005
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    Django(三十二)—— Django中建表方式
    Django(三十一)——Django ORM常用字段类型
    IIS部署muweb(需主程序 4.0.0以上版本)
    吴裕雄--天生自然--SPRINGBOOT开发实战学习笔记--创建用户表
    吴裕雄--天生自然--SpringBoot开发实战学习笔记--Oracle11g创建表空间和用户并授权
    吴裕雄--天生自然--SpringBoot开发实战学习笔记--spring boot打jar包发布的方法
    吴裕雄--天生自然--SpringBoot开发实战学习笔记--处理Error creating bean with name 'springApplicationAdminRegistrar' defined in class path resource
    吴裕雄--天生自然--SpringBoot开发实战学习笔记--配置MAVEN
    吴裕雄--天生自然SPRINGBOOT开发实战--SpringBoot 打包
    吴裕雄--天生自然SPRINGBOOT开发实战--SpringBoot DevTools
  • 原文地址:https://www.cnblogs.com/zgq123456/p/9897586.html
Copyright © 2011-2022 走看看