日常开发过程中,我们涉及到的主要是解码相关内容,但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