FFmpeg转码dash/hls等格式

本文以aac数据的转码为例,其他编码格式类似,关键是明白FFmpeg转码的原理,具体实现大同小异,转码的流程如下:

在这里插入图片描述

主要使用的函数为:
  • avformat_write_header()向目标文件写入文件头
  • av_write_frame()/av_interleaved_write_frame()向目标文件写入数据
  • av_write_trailer()结束写入目标文件
主要流程如下(以下称转码器为muxer):
  • 首先调用avformat_alloc_context()来创建muxing上下文。
    这里初始化了AVFormatContext(格式配置);第二个参数可以是一个AVFormat实例,用来决定视频/音频格式;第三个参数是一个描述格式的字符串,如“h264″、 "mpeg"等;第四个是filename,从它的扩展名可以推断应该使用的格式(在第三个参数为null的情况下)。
AVFormatContext* oc;
如转为dash
avformat_alloc_output_context2(&oc, NULL, "dash", "/home/dash/index.mpd");
转为hls 
avformat_alloc_output_context2(&oc, NULL, "hls", "/home/hls/master.m3u8");
  • 通过填充此上下文中的各个字段来设置muxer:必须设置oformat字段以选择要使用的muxer。
    除非格式为AVFMT_NOFILE类型,否则必须将pb字段设置为打开的IO上下文,可以是从avio_open()返回的,也可以是自定义的。
    除非格式是AVFMT_NOSTREAMS类型,否则必须使用avformat_new_stream()函数创建至少一个流。
    此外应填写编解码器上下文信息,如编解码器类型、id和其他已知参数(如宽度/高度、像素或样本格式等)。stream timebase应设置为调用者希望用于此流的timebase。
    以下为添加一个输出stream的代码:
/* Add an output stream. */
static void add_stream(OutputStream *ost, AVFormatContext *oc, AVCodec **codec,
                       enum AVCodecID codec_id) {
  AVCodecContext *c;
  int i;
  /* find the encoder */
  *codec = avcodec_find_encoder(codec_id);
  if (!(*codec)) {
    fprintf(stderr, "Could not find encoder for '%s'\n",
            avcodec_get_name(codec_id));
    exit(1);
  }
  ost->st = avformat_new_stream(oc, NULL);
  if (!ost->st) {
    fprintf(stderr, "Could not allocate stream\n");
    exit(1);
  }
  ost->st->id = oc->nb_streams - 1;
  c = avcodec_alloc_context3(*codec);
  if (!c) {
    fprintf(stderr, "Could not alloc an encoding context\n");
    exit(1);
  }
  ost->enc = c;
  switch ((*codec)->type) {
    case AVMEDIA_TYPE_AUDIO:
      c->sample_fmt =
          (*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
      c->bit_rate = 64000;
      c->sample_rate = 44100;
      if ((*codec)->supported_samplerates) {
        c->sample_rate = (*codec)->supported_samplerates[0];
        for (i = 0; (*codec)->supported_samplerates[i]; i++) {
          if ((*codec)->supported_samplerates[i] == 44100)
            c->sample_rate = 44100;
        }
      }
      c->channels = av_get_channel_layout_nb_channels(c->channel_layout);
      c->channel_layout = AV_CH_LAYOUT_STEREO;
      if ((*codec)->channel_layouts) {
        c->channel_layout = (*codec)->channel_layouts[0];
        for (i = 0; (*codec)->channel_layouts[i]; i++) {
          if ((*codec)->channel_layouts[i] == AV_CH_LAYOUT_STEREO)
            c->channel_layout = AV_CH_LAYOUT_STEREO;
        }
      }
      c->channels = av_get_channel_layout_nb_channels(c->channel_layout);
      ost->st->time_base = AVRational{1, c->sample_rate};
      break;
    case AVMEDIA_TYPE_VIDEO:
      c->codec_id = codec_id;
      c->bit_rate = 400000;
      /* Resolution must be a multiple of two. */
      c->width = 352;
      c->height = 288;
      /* timebase: This is the fundamental unit of time (in seconds) in terms
       * of which frame timestamps are represented. For fixed-fps content,
       * timebase should be 1/framerate and timestamp increments should be
       * identical to 1. */
      ost->st->time_base = AVRational{1, STREAM_FRAME_RATE};
      c->time_base = ost->st->time_base;
      c->gop_size = 12; /* emit one intra frame every twelve frames at most */
      c->pix_fmt = STREAM_PIX_FMT;
      if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
        /* just for testing, we also add B-frames */
        c->max_b_frames = 2;
      }
      if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
        /* Needed to avoid using macroblocks in which some coeffs overflow.
         * This does not happen with normal video, it just happens here as
         * the motion of the chroma plane does not match the luma plane. */
        c->mb_decision = 2;
      }
      break;
    default:
      break;
  }
  /* Some formats want stream headers to be separate. */
  if (oc->oformat->flags & AVFMT_GLOBALHEADER)
    c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
  • 在创建好stream以后可以通过av_opt_set_int()、av_opt_set_double()等进行转码设置:
AVFormatContext* oc;
av_opt_set(oc->priv_data, "preset", "superfast", 0);    //设置preset
av_opt_set(oc->priv_data, "tune", "zerolatency", 0);   //设置tune

//转码为dash时,同时生成m3u8索引文件
av_opt_set_int(oc->priv_data, "hls_playlist", 1, AV_OPT_SEARCH_CHILDREN);  

//转码为dash时,设置切片长度,其中seg_duration单位为us
 av_opt_set_int(oc->priv_data, "use_template ", 1, AV_OPT_SEARCH_CHILDREN);
 av_opt_set_int(oc->priv_data, "seg_duration", 5000000, AV_OPT_SEARCH_CHILDREN);

//转码为hls时,设置切片长度
av_opt_set_int(oc->priv_data, "hls_time",10, AV_OPT_SEARCH_CHILDREN);
  • 一般来说,实际音频转码过程中我们大多情况下需要对声道数(channels)采样率(sample rate)采样长度(AVSampleFormat)等等参数进行调整,这时候就不能直接对解码后的pcm编码成我们想要的编码格式,于是有了重采样。(如果是视频直接使用sws_scale对图像进行格式转换即可)
//ReSample:out_frame 为输出的重采样后的frame
int ReSample(AVFrame *in_frame, AVFrame *out_frame, int audio_index) {
  int ret;
  int max_dst_nb_samples = 4096;
  int64_t src_nb_samples = in_frame->nb_samples;
  out_frame->pts = in_frame->pts;
  uint8_t *paudiobuf;
  int decode_size, input_size, len;
  if (pSwrCtx != NULL) {
    out_frame->nb_samples = av_rescale_rnd(
        swr_get_delay(pSwrCtx, ofmt_ctx->streams[audio_index]->codec->sample_rate) + src_nb_samples,
        ofmt_ctx->streams[audio_index]->codec->sample_rate,
        in_sample_rate,
        AV_ROUND_UP);

    ret = av_samples_alloc(
        out_frame->data, &out_frame->linesize[0],
        ofmt_ctx->streams[audio_index]->codec->channels, out_frame->nb_samples,
        ofmt_ctx->streams[audio_index]->codec->sample_fmt, 0);

    if (ret < 0) {
      av_log(NULL, AV_LOG_WARNING,
             "[%s.%d %s() Could not allocate samples Buffer\n", __FILE__,
             __LINE__, __FUNCTION__);
      return -1;
    }

    max_dst_nb_samples = out_frame->nb_samples;

    uint8_t *m_ain[32];
    setup_array(m_ain, in_frame,
                in_sample_fmt,
                src_nb_samples);

    len = swr_convert(pSwrCtx, out_frame->data, out_frame->nb_samples,
                      (const uint8_t **)in_frame->data, src_nb_samples);
    if (len < 0) {
      char errmsg[BUF_SIZE_1K];
      av_strerror(len, errmsg, sizeof(errmsg));
      av_log(NULL, AV_LOG_WARNING, "[%s:%d] swr_convert!(%d)(%s)", __FILE__,
             __LINE__, len, errmsg);
      return -1;
    }
  } else {
    printf("pSwrCtx with out init!\n");
    return -1;
  }
  return 0;
}
  • muxing上下文完全设置好后,调用avformat_write_header()来初始化muxer内部并写入文件头。
ret = avformat_write_header(oc, NULL);
  • 接着反复调用av_interleaved_write_frame(),将数据发送到muxer。发送到muxer的包的计时信息必须在相应AVStream的时基中。
//将一帧数据写入输出文件中
static int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c,
                       AVStream *st, AVFrame *frame) {
  int ret;
  // send the frame to the encoder
  ret = avcodec_send_frame(c, frame);
  if (ret < 0) {
    fprintf(stderr, "Error sending a frame to the encoder: %s\n",
            wrap_av_err2str(ret));
    exit(1);
  }
  while (ret >= 0) {
    AVPacket pkt = {0};
    ret = avcodec_receive_packet(c, &pkt);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
      break;
    else if (ret < 0) {
      fprintf(stderr, "Error encoding a frame: %s\n", wrap_av_err2str(ret));
      exit(1);
    }
    /* rescale output packet timestamp values from codec to stream timebase */
    av_packet_rescale_ts(&pkt, c->time_base, st->time_base);
    pkt.stream_index = st->index;
    /* Write the compressed frame to the media file. */
    log_packet(fmt_ctx, &pkt);
    ret = av_interleaved_write_frame(fmt_ctx, &pkt);
    av_packet_unref(&pkt);
    if (ret < 0) {
      //fprintf(stderr, "Error while writing output packet: %s\n",
      //        av_err2str(ret));
      exit(1);
    }
  }
  return ret == AVERROR_EOF ? 1 : 0;
}

写入所有数据后,调用av_write_trailer()刷新所有缓冲数据包并完成输出文件,然后关闭IO上下文(如果有),最后使用avformat_free_context()释放muxing上下文。

  /* Write the trailer, if any. The trailer must be written before you
   * close the CodecContexts open when you wrote the header; otherwise
   * av_write_trailer() may try to use memory that was freed on
   * av_codec_close(). */
  av_write_trailer(oc);
  /* Close each codec. */
  if (have_video) close_stream(oc, &video_st);
  if (have_audio) close_stream(oc, &audio_st);
  if (!(fmt->flags & AVFMT_NOFILE)) /* Close the output file. */
    avio_closep(&oc->pb);
  /* free the stream */
  avformat_free_context(oc);

转码结果:
在这里插入图片描述

播放截图(windows上这个.tmp文件才能播放,linux上直接播放.m3u8):
在这里插入图片描述

详细代码可到此处下载:https://download.csdn.net/download/qq_37984341/12553625

ffmpeg是一个开源的跨平台音视频处理工具,能够对音视频进行编解码转码、剪辑、合成等操作。它支持多种音视频格式,并且具有强大的功能和灵活性。 python-ffmpeg-video-streaming是一个Python库,用于打包媒体内容以进行在线流式传输,如DASHHLS。它提供了简单易用的API,可以方便地实现视频流的处理和传输。 imageio_ffmpeg是一个用于处理图片和视频的Python库。它提供了对FFmpeg的封装,使得在Python中可以方便地使用FFmpeg进行图片和视频的处理。通过使用imageio_ffmpeg,可以实现图片和视频的解码编码、剪辑等功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Python库 | python-ffmpeg-video-streaming-0.0.11.tar.gz](https://download.csdn.net/download/qq_38161040/85129360)[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* [python-ffmpeg-video-streaming::videocassette:使用FFmpeg打包媒体内容以进行在线流式传输(DASHHLS)](https://download.csdn.net/download/weixin_42105816/18373506)[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* [Python库 | imageio_ffmpeg-0.4.1-py3-none-win32.whl](https://download.csdn.net/download/qq_38161040/85514865)[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、付费专栏及课程。

余额充值