3

我已经开始学习 ffmpeg 几个弱点。目前,我可以使用 h264/AVC 编解码器将任何视频转码为 mp4。主要方案是这样的:

-open input
-demux
-decode
-encode
-mux

实际代码如下:

#include <iostream>

#include <math.h>

extern "C" {

  #
  ifndef __STDC_CONSTANT_MACROS# undef main /* Prevents SDL from overriding main() */ # define __STDC_CONSTANT_MACROS# endif

  # pragma comment(lib, "avcodec.lib")# pragma comment(lib, "avformat.lib")# pragma comment(lib, "swscale.lib")# pragma comment(lib, "avutil.lib")

  #include <libavcodec\avcodec.h>
  #include <libavformat\avformat.h>
  #include <libswscale\swscale.h>
  #include <libavutil\mem.h>
  #include <libavutil/opt.h>
  #include <libavutil\channel_layout.h>
  #include <libavutil\common.h>
  #include <libavutil\imgutils.h>
  #include <libavutil\mathematics.h>
  #include <libavutil\samplefmt.h>
}

using namespace std;

void open_video(AVFormatContext * oc, AVCodec * codec, AVStream * st) {
  int ret;
  AVCodecContext * c;
  c = st - > codec;

  /*open codec */

  cout << "probably starts here" << endl;
  ret = avcodec_open2(c, codec, NULL);
  cout << "and ends here" << endl;

  if (ret < 0) {
    cout << ("Could not open video codec") << endl;
  }

}

/*This function will add a new stream to our file.
@param
oc -> Format context that the new stream will be added.
codec -> codec of the stream, this will be passed.
codec_id -> 
chWidth->
chHeight->
*/

AVStream * addStream(AVFormatContext * oc, AVCodec ** codec, enum AVCodecID codec_id, int chWidth, int chHeight, int fps) {
  AVCodecContext * c;
  AVStream * st;

  //find encoder of the stream, it passes this information to @codec, later on
  //it will be used in encoding the video @ avcodec_encode_video2 in loop.
  * codec = avcodec_find_encoder(AV_CODEC_ID_H264);

  if (( * codec) == NULL)
    cout << "ERROR CAN NOT FIND ENCODER! ERROR! ERROR! AVCODEC_FIND_ENCODER FAILED !!!1 "
  "" << endl;

  if (!( * codec))
    printf("Could not find encoder for ' %s ' ", avcodec_get_name(codec_id));

  //create a new stream with the found codec inside oc(AVFormatContext).
  st = avformat_new_stream(oc, * codec);

  if (!st)
    cout << " Cannot allocate stream " << endl;

  //Setting the stream id.
  //Since, there can be other streams in this AVFormatContext,
  //we should find the first non used index. And this is oc->nb_streams(number of streams) - 1
  st - > id = oc - > nb_streams - 1;

  c = st - > codec;

  //setting the stream's codec's properties.
  c - > codec_id = codec_id;
  c - > bit_rate = 4000000;
  c - > width = chWidth;
  c - > height = chHeight;
  c - > time_base.den = fps;
  //fps;
  c - > time_base.num = 1;
  c - > gop_size = 12;
  c - > pix_fmt = AV_PIX_FMT_YUV420P;

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

  /* Some formats want stream headers to be separate. */
  if (oc - > oformat - > flags & AVFMT_GLOBALHEADER)
    c - > flags |= CODEC_FLAG_GLOBAL_HEADER;

  //returning our lovely new brand stream.
  return st;

}

int changeResolution(string source, int format) {
  //Data members
  struct SwsContext * sws_ctx = NULL;
  AVFrame * pFrame = NULL;
  AVFrame * outFrame = NULL;
  AVPacket packet;
  uint8_t * buffer = NULL;
  uint8_t endcode[] = {
    0,
    0,
    1,
    0xb7
  };
  AVDictionary * optionsDict = NULL;
  AVFormatContext * pFormatCtx = NULL;
  AVFormatContext * outputContext = NULL;
  AVCodecContext * pCodecCtx;
  AVCodec * pCodec;
  AVCodec * codec;
  AVCodec * videoCodec;
  AVOutputFormat * fmt;
  AVStream * video_stream;
  int changeWidth;
  int changeHeight;
  int frameFinished;
  int numBytes;
  int fps;

  int lock = 0;

  //Register all codecs & other important stuff. Vital!..
  av_register_all();

  //Selects the desired resolution.
  if (format == 0) {
    changeWidth = 320;
    changeHeight = 180;
  } else if (format == 1) {
    changeWidth = 640;
    changeHeight = 480;

  } else if (format == 2) {
    changeWidth = 960;
    changeHeight = 540;

  } else if (format == 3) {
    changeWidth = 1024;
    changeHeight = 768;

  } else {
    changeWidth = 1280;
    changeHeight = 720;
  }

  // Open video file
  int aaa;
  aaa = avformat_open_input( & pFormatCtx, source.c_str(), NULL, NULL);
  if (aaa != 0) {
    cout << " cannot open input file \n" << endl;
    cout << "aaa = " << aaa << endl;
    return -1; // Couldn't open file    
  }

  // Retrieve stream information
  if (av_find_stream_info(pFormatCtx) < 0)
    return -1; // Couldn't find stream information

  //just checking duration casually for no reason
  /*int64_t duration = pFormatCtx->duration;

  cout << "the duration is " << duration << " " << endl;*/

  //this writes the info about the file
  av_dump_format(pFormatCtx, 0, 0, 0);
  cin >> lock;

  // Find the first video stream
  int videoStream = -1;
  int i;

  for (i = 0; i < 3; i++)
    if (pFormatCtx - > streams[i] - > codec - > codec_type == AVMEDIA_TYPE_VIDEO) {
      videoStream = i;
      cout << " lel \n ";
      break;

    }

  if (videoStream == -1)
    return -1; // Didn't find a video stream

  // Get a pointer to the codec context for the video stream
  pCodecCtx = pFormatCtx - > streams[videoStream] - > codec;
  fps = pCodecCtx - > time_base.den;

  //Find the decoder of the input file, for the video stream
  pCodec = avcodec_find_decoder(pCodecCtx - > codec_id);

  if (pCodec == NULL) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }

  // Open codec, you must open it first, in order to use it.
  if (avcodec_open2(pCodecCtx, pCodec, & optionsDict) < 0)
    return -1; // Could not open codec

  // Allocate video frame ( pFrame for taking the packets into, outFrame for processed frames to packet.)
  pFrame = avcodec_alloc_frame();
  outFrame = avcodec_alloc_frame();

  i = 0;

  int ret;
  int video_frame_count = 0;

  //Initiate the outFrame set the buffer & fill the properties
  numBytes = avpicture_get_size(PIX_FMT_YUV420P, changeWidth, changeHeight);
  buffer = (uint8_t * ) av_malloc(numBytes * sizeof(uint8_t));
  avpicture_fill((AVPicture * ) outFrame, buffer, PIX_FMT_YUV420P, changeWidth, changeHeight);

  int pp;
  int frameNo = 0;

  //allocate the outputContext, it will be the AVFormatContext of our output file. 
  //It will try to find the format by giving the file name.
  avformat_alloc_output_context2( & outputContext, NULL, NULL, "myoutput.mp4");

  //Cant find the file extension, using MPEG as default.
  if (!outputContext) {
    printf("Could not deduce output format from file extension: using MPEG.\n");
    avformat_alloc_output_context2( & outputContext, NULL, "mpeg", "myoutput.mp4");
  }

  //Still cant set file extension, exit.
  if (!outputContext) {
    return 1;
  }

  //set AVOutputFormat fmt to our outputContext's format.
  fmt = outputContext - > oformat;
  video_stream = NULL;

  //If fmt has a valid codec_id, create a new video stream.
  //This function will set the streams codec & codecs desired properties.
  //Stream's codec will be passed to videoCodec for later usage.
  if (fmt - > video_codec != AV_CODEC_ID_NONE)
    video_stream = addStream(outputContext, & videoCodec, fmt - > video_codec, changeWidth, changeHeight, fps);

  //open the video using videoCodec. by avcodec_open2() i.e open the codec.
  if (video_stream)
    open_video(outputContext, videoCodec, video_stream);

  //Creating our new output file.
  if (!(fmt - > flags & AVFMT_NOFILE)) {
    ret = avio_open( & outputContext - > pb, "toBeStreamed.264", AVIO_FLAG_WRITE);
    if (ret < 0) {
      cout << " cant open file " << endl;
      return 1;
    }
  }

  //Writing the header of format context.
  //ret = avformat_write_header(outputContext, NULL);

  if (ret >= 0) {
    cout << "writing header success !!!" << endl;
  }

  //Start reading packages from input file.
  while (av_read_frame(pFormatCtx, & packet) >= 0) {

    // Is this a packet from the video stream?  
    if (packet.stream_index == videoStream) {

      // Decode video package into frames
      ret = avcodec_decode_video2(pCodecCtx, pFrame, & frameFinished, & packet);

      if (ret < 0) {
        printf(" Error decoding frame !!..");
        return ret;
      }

      if (frameFinished) {
        printf("video_frame n:%d    coded_n:%d\n", video_frame_count++, pFrame - > coded_picture_number);
      }

      av_free_packet( & packet);

      //do stuff with frame, in this case we are changing the resolution.
      static struct SwsContext * img_convert_ctx_in = NULL;
      if (img_convert_ctx_in == NULL) {
        img_convert_ctx_in = sws_getContext(pCodecCtx - > width,
          pCodecCtx - > height,
          pCodecCtx - > pix_fmt,
          changeWidth,
          changeHeight,
          PIX_FMT_YUV420P,
          SWS_BICUBIC,
          NULL,
          NULL,
          NULL);

      }
      //scale the frames
      sws_scale(img_convert_ctx_in,
        pFrame - > data,
        pFrame - > linesize,
        0,
        pCodecCtx - > height,
        outFrame - > data,
        outFrame - > linesize);

      //initiate the pts value
      if (frameNo == 0)
        outFrame - > pts = 0;

      //calculate the pts value & set it.
      outFrame - > pts += av_rescale_q(1, video_stream - > codec - > time_base, video_stream - > time_base);

      //encode frames into packages. Package passed in @packet.
      if (avcodec_encode_video2(outputContext - > streams[0] - > codec, & packet, outFrame, & pp) < 0)
        cout << "Encoding frames into packages, failed. " << endl;

      frameNo++;

      //write the packages into file, resulting in creating a video file.
      av_interleaved_write_frame(outputContext, & packet);

    }

  }

  av_free_packet( & packet);
  //av_write_trailer(outputContext);

  avio_close(outputContext - > pb);

  // Free the RGB image
  av_free(buffer);
  av_free(outFrame);

  // Free the YUV frame
  av_free(pFrame);

  // Close the codec
  avcodec_close(video_stream - > codec);
  avcodec_close(pCodecCtx);

  // Close the video file
  avformat_close_input( & pFormatCtx);

  return 0;
}

在该过程结束时,我得到了我想要的具有所需编解码器和容器和分辨率的文件。

我的问题是,在我们项目的一部分中,我需要在文件中获取基本视频流。比如example.264。但是,我不能在不创建AVFormatContext的情况下添加流。据我所知,我无法创建 AVFormatContext 因为 264 文件没有容器,它们只是原始视频?

我已经尝试了使用fwrite的解码编码.c 的方式。但是,该示例适用于 mpeg-2 编解码器,当我尝试将该代码改编为 H264/AVC 编解码器时,我从 mediainfo 收到“浮点除以零”错误,此外,视频的某些属性没有显示(例如作为 FPS & 游戏时间 & 质量因素)。我认为这与示例在代码末尾添加的“endcode”有关。它适用于 mpeg-2。( uint8_t endcode[] = { 0, 0, 1, 0xb7 }; )

无论如何,我很想为这项任务找到一个起点。我已经通过使用互联网资源(ffmpeg 很少且已过时)设法走到了这一步,但现在我有点卡住了。

4

0 回答 0