音视频入门系列-FFmpeg篇(编码)

日常开发过程中,我们涉及到的主要是解码相关内容,但FFmpeg同样具有编码的能力。今天这篇文章简单介绍下FFmpeg编码的内容。

1.FFmpeg编码视频

使用FFmpeg库编码YUV,代码及调用逻辑如下(文末附代码):

图片

2.FFmpeg编码音频

使用FFmpeg库编码PCM,代码及调用逻辑如下(文末附代码):

图片

 3.编码YUV源码如下:

/* 
  author:八小时码字员 
  file:encode_yuv_to_h264.cpp
 */
 
#include "encode_yuv_to_h264.h"
#include "output_log.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#define __STDC_CONSTANT_MACROS
extern "C"
{
  #include <libavcodec/avcodec.h>
  #include <libavutil/opt.h>
  #include <libavutil/imgutils.h>
}

static void encode(AVCodecContext* pCodecCtx, AVFrame *pFrame, AVPacket* pPacket, FILE* p_output_f)
{
  int ret;
  ret = avcodec_send_frame(pCodecCtx, pFrame);
  while (ret >= 0)
  {
    ret = avcodec_receive_packet(pCodecCtx, pPacket);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
      return;
    fwrite(pPacket->data, 1, pPacket->size, p_output_f);
    av_packet_unref(pPacket);
  }
}

//codec_name="libx264"
int encode_yuv_to_h264(const char* output_filePath)
{
  AVCodecContext* pCodecCtx = NULL;
  const AVCodec* pCodec = NULL;
  AVPacket* pPacket = NULL;
  AVFrame* pFrame = NULL;
  char codec_name[] = "libx264";
  unsigned char endcode[] = { 0x00, 0x00, 0x01, 0x7b };
  FILE* p_output_f = NULL;
  int i, x, y;
  int ret = 0;

  pCodec = avcodec_find_encoder_by_name(codec_name);
  //pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
  if (!pCodec)
  {
    output_log(LOG_ERROR, "avcodec_find_encoder_by_name error, codec_name=%s", codec_name);
    ret = -1;
    goto end;
  }    
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if (!pCodecCtx)
  {
    output_log(LOG_ERROR, "avcodec_alloc_context3 error, pCodecCtx is NULL");
    ret = -1;
    goto end;
  }
  pPacket = av_packet_alloc();
  pFrame = av_frame_alloc();

  //set AVCodecContext parameters
  pCodecCtx->bit_rate = 400000;
  pCodecCtx->width = 352;
  pCodecCtx->height = 288;
  pCodecCtx->time_base = { 1, 25 };
  pCodecCtx->framerate = { 25, 1 };
  /* emit one intra frame every ten frames
   * check frame pict_type before passing frame
   * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
   * then gop_size is ignored and the output of encoder
   * will always be I frame irrespective to gop_size
   */
  pCodecCtx->gop_size = 10;
  pCodecCtx->max_b_frames = 1;
  pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
  if (pCodec->id == AV_CODEC_ID_H264)
    av_opt_set(pCodecCtx->priv_data, "preset", "slow", 0);

  //open codec
  if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
  {
    ret = -1;
    goto end;
  }

  pFrame->format = pCodecCtx->pix_fmt;
  pFrame->width = pCodecCtx->width;
  pFrame->height = pCodecCtx->height;
  //Allocate new buffer(s) for audio or video data.
  if (av_frame_get_buffer(pFrame, 32) < 0)
  {
    output_log(LOG_ERROR, "av_frame_get_buffer error");
    ret = -1;
    goto end;
  }
  
  //open output_file
  fopen_s(&p_output_f, output_filePath, "wb");
  if (!p_output_f)
  {
    ret = -1;
    goto end;
  }
  
  //encode 5 seconds of video
  for (i = 0; i < 25 * 5; i++)
  {
    fflush(stdout);

    //make sure the frame data is writeable
    if (av_frame_is_writable(pFrame) < 0)
    {
      ret = -1;
      goto end;
    }

    //Y
    for (y = 0; y < pCodecCtx->height; y++)
    {
      for (x = 0; x < pCodecCtx->width; x++)
      {
        pFrame->data[0][y*pFrame->linesize[0] + x] = x + y + i * 3;
      }
    }
    //Y and V
    for (y = 0; y < pCodecCtx->height / 2; y++) 
    {
      for (x = 0; x < pCodecCtx->width / 2; x++) 
      {
        pFrame->data[1][y * pFrame->linesize[1] + x] = 128 + y + i * 2;
        pFrame->data[2][y * pFrame->linesize[2] + x] = 64 + x + i * 5;
      }
    }

    pFrame->pts = i;

    //encode this img
    encode(pCodecCtx, pFrame, pPacket, p_output_f);
  }

  //flush the encoder
  encode(pCodecCtx, NULL, pPacket, p_output_f);
  
  //add sequence end code to have a real MPEG file
  fwrite(endcode, 1, sizeof(endcode), p_output_f);
  
  fclose(p_output_f);

end:
  if (pCodecCtx)
    avcodec_free_context(&pCodecCtx);
  if (pPacket)
    av_packet_free(&pPacket);
  if (pFrame)
    av_frame_free(&pFrame);
  printf("=============== encode_yuv_to_h264 done ===============\n");
  return ret;
}

4.编码PCM源码如下:

/* 
  author:八小时码字员 
  file:encode_pcm_to_pm2.cpp
 */

#include "encode_pcm_to_mp2.h"
#include "output_log.h"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

extern "C"
{
  #include <libavcodec/avcodec.h>
  #include <libavutil/channel_layout.h>
  #include <libavutil/common.h>
  #include <libavutil/frame.h>
  #include <libavutil/samplefmt.h>
}

/* check that a given sample format is supported by the encoder */
static int check_sample_fmt(const AVCodec *pCodec, enum AVSampleFormat sample_fmt)
{
  const enum AVSampleFormat *p = pCodec->sample_fmts;

  while (*p != AV_SAMPLE_FMT_NONE) 
  {
    if (*p == sample_fmt)
      return 1;
    p++;
  }
  return 0;
}

/* just pick the highest supported samplerate */
static int select_sample_rate(const AVCodec *pCodec)
{
  const int *p;
  int best_samplerate = 0;

  if (!pCodec->supported_samplerates)
    return 44100;

  p = pCodec->supported_samplerates; //< array of supported audio samplerates, or NULL if unknown, array is terminated by 0
  while (*p)
  {
    if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
      best_samplerate = *p;
    p++;
  }
  return best_samplerate;
}

/* select layout with the highest channel count */
static int select_channel_layout(const AVCodec *codec)
{
  const uint64_t *p;
  uint64_t best_ch_layout = 0;
  int best_nb_channels = 0;

  if (!codec->channel_layouts)
    return AV_CH_LAYOUT_STEREO;

  p = codec->channel_layouts; //< array of support channel layouts, or NULL if unknown. array is terminated by 0
  while (*p)
  {
    int nb_channels = av_get_channel_layout_nb_channels(*p);

    if (nb_channels > best_nb_channels) 
    {
      best_ch_layout = *p;
      best_nb_channels = nb_channels;
    }
    p++;
  }
  return best_ch_layout;
}

static void encode(AVCodecContext* pCodecCtx, AVFrame *pFrame, AVPacket* pPacket, FILE* p_output_f)
{
  int ret;
  ret = avcodec_send_frame(pCodecCtx, pFrame);
  while (ret >= 0)
  {
    ret = avcodec_receive_packet(pCodecCtx, pPacket);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
      return;
    fwrite(pPacket->data, 1, pPacket->size, p_output_f);
    av_packet_unref(pPacket);
  }
}

int encode_pcm_to_mp2(const char* output_filepath)
{
  AVCodecContext* pCodecCtx = NULL;
  const AVCodec* pCodec = NULL;
  AVPacket* pPacket = NULL;
  AVFrame* pFrame = NULL;
  FILE* p_output_f = NULL;
  int i, j, k, ret = 0;
  uint16_t *samples;
  float t, tincr;

  pCodec = avcodec_find_encoder(AV_CODEC_ID_MP2);
  if (!pCodec)
  {
    output_log(LOG_ERROR, "avcodec_find_encoder(AV_CODEC_ID_MP2) error");
    ret = -1;
    goto end;
  }
  pCodecCtx = avcodec_alloc_context3(pCodec);
  pPacket = av_packet_alloc();
  if (!pPacket)
  {
    output_log(LOG_ERROR, "av_packet_alloc error");
    ret = -1;
    goto end;
  }
  pFrame = av_frame_alloc();
  if (!pFrame)
  {
    output_log(LOG_ERROR, "av_frame_alloc error");
    ret = -1;
    goto end;
  }

  //set AVCodecContext parameters
  pCodecCtx->bit_rate = 64000;
  /* check that the encoder supports s16 pcm input */
  pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
  if (!check_sample_fmt(pCodec, pCodecCtx->sample_fmt))
  {
    output_log(LOG_ERROR, "check_sample_fmt error");
    ret = -1;
    goto end;
  }
  /* select other audio parameters supported by the encoder */
  pCodecCtx->sample_rate = select_sample_rate(pCodec);
  pCodecCtx->channel_layout = select_channel_layout(pCodec);
  pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);

  //open codec
  if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
  {
    ret = -1;
    goto end;
  }

  //set AVFrame parameters
  pFrame->nb_samples = pCodecCtx->frame_size;
  pFrame->format = pCodecCtx->sample_fmt;
  pFrame->channel_layout = pCodecCtx->channel_layout;
  if (av_frame_get_buffer(pFrame, 0) < 0)
  {
    output_log(LOG_ERROR, "av_frame_get_buffer error");
    ret = -1;
    goto end;
  }

  fopen_s(&p_output_f, output_filepath, "wb");
  if (!p_output_f)
  {
    output_log(LOG_ERROR, "fopen_s error");
    ret = -1;
    goto end;
  }

  //encode a single tone sound
  t = 0;
  tincr = 2 * M_PI * 440.0 / pCodecCtx->sample_rate;
  for (i = 0; i < 200; i++)
  {
    /* make sure the frame is writable -- makes a copy if the encoder
    * kept a reference internally */
    if (av_frame_make_writable(pFrame) < 0)
    {
      output_log(LOG_ERROR, "av_frame_make_writable error");
      ret = -1;
      goto end;
    }
    samples = (uint16_t*)pFrame->data[0];

    for (j = 0; j < pCodecCtx->frame_size; j++)
    {
      samples[2 * j] = (int)(sin(t) * 10000);

      for (k = 1; k < pCodecCtx->channels; k++)
      {
        samples[2 * j + k] = samples[2 * j];
      }
      t += tincr;
    }
    encode(pCodecCtx, pFrame, pPacket, p_output_f);
  }

  //flush the encoder 
  encode(pCodecCtx, NULL, pPacket, p_output_f);

  fclose(p_output_f);

end:
  if (pCodecCtx)
    avcodec_free_context(&pCodecCtx);
  if (pPacket)
    av_packet_free(&pPacket);
  if (pFrame)
    av_frame_free(&pFrame);
  printf("=============== encode_pcm_to_mp2 done ===============\n");
  return ret;
}

音视频入门系列文章已同步在微信公众号(可扫下方二维码关注):八小时码字员

音视频入门系列,同步录制了学习视频,已上传至bilibili(八小时码字员):音视频入门系列(图像、音频、字幕、视频封装格式,FFmpeg、ffplay源码分析,解码、编码、转码,流媒体协议,服务器部署)_哔哩哔哩_bilibili

音视频学习交流QQ群:693316541

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值