fmpeg_sample解读_encode_audio
2020-10-23 本文已影响0人
刘佳阔
title: ffmpeg_sample解读_encode_audio
date: 2020-10-23 10:15:02
tags: [读书笔记]
typora-copy-images-to: ./imgs
typora-root-url: ./imgs
总结
随机生成音频数据.然后编码成帧.送入编码生成packet.在写入文件.自己制定音频的码率bit_rate,采样位数sample_fmt,采样率sample_rate,声道数channel和声道布局channel_layout
流程图
graph TB
findEncoder[avcodec_find_encoder]
-->allocContext[avcodec_alloc_context3]
-->setContextParam[设置编码上下文参数]
-->codecOpen[avcodec_open2]
-->packet[av_packet_alloc]
-->frame[av_frame_alloc]
-->buff[av_frame_get_buffer]
-->writeable[av_frame_make_writable]
-->encode[encode]
-->sendFrame{avcodec_send_frame>0?}
-->|yes|readPacket[avcodec_receive_packet]
-->fwrite[fwrite]
-->unref[av_packet_unref]
-->release[release]
sendFrame-->|no|no
no-->release
image-20201023153653504
代码
/**
* @file
* audio encoding with libavcodec API example.
*
* @example encode_audio.c
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#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 *codec, enum AVSampleFormat sample_fmt) {
const enum AVSampleFormat *p = codec->sample_fmts;
while (*p != AV_SAMPLE_FMT_NONE) {
if (*p == sample_fmt)
return 1;
p++;
}
return 0;
}
/**
* 找到最匹配 编码器支持的采样率最接近的值 supported_samplerates
* just pick the highest supported samplerate */
static int select_sample_rate(const AVCodec *codec) {
const int *p;
int best_samplerate = 0;
//找到离44100最佳的采样率
if (!codec->supported_samplerates)
return 44100;
p = codec->supported_samplerates;
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;
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 *ctx, AVFrame *frame, AVPacket *pkt,
FILE *output) {
int ret;
//原生数据送入编码器,得到packet,如果 frame传null.就会flush 编码器中遗留的数据.所以最后总要才传个null进来
/* send the frame for encoding */
ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
fprintf(stderr, "Error sending the frame to the encoder\n");
exit(1);
}
/* read all the available output packets (in general there may be any
* number of them */
while (ret >= 0) {
//从编码器中拿到能用的编码后的packet .写到文件中
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error encoding audio frame\n");
exit(1);
}
//把pkt.data中的数据.以 1字节为单位,一共写入size个字节.到output文件中,同时更新文件指针到写入的末尾
fwrite(pkt->data, 1, pkt->size, output);
//是否packet以便再次写入
av_packet_unref(pkt);
}
}
/**
* 编码音频 随机生成音频文件.组成frame.送如编码器编码后生成packet.写入文件
* @param argc
* @param argv
* @return
* avcodec_find_encoder:根据指定的AVCodecID查找注册的解码器。
avcodec_alloc_context3:为AVCodecContext分配内存。
avcodec_open2:打开解码器。
avcodec_send_frame:将AVFrame非压缩数据给编码器。详细介绍见FFmpeg音频解码的编解码API介绍部分。
avcodec_receive_packet:获取到编码后的AVPacket数据。
av_frame_get_buffer: 为音频或视频数据分配新的buffer。在调用这个函数之前,必须在AVFame上设置好以下属性:format(视频为像素格式,音频为样本格式)、nb_samples(样本个数,针对音频)、channel_layout(通道类型,针对音频)、width/height(宽高,针对视频)。
av_frame_make_writable:确保AVFrame是可写的,尽可能避免数据的复制。
如果AVFrame不是是可写的,将分配新的buffer和复制数据。
链接:https://www.jianshu.com/p/c6154e106b8c
*/
int encode_audio_main(int argc, char **argv) {
const char *filename;
const AVCodec *codec;
AVCodecContext *c = NULL;
AVFrame *frame;
AVPacket *pkt;
int i, j, k, ret;
FILE *f;
uint16_t *samples;
float t, tincr;
if (argc <= 1) {
fprintf(stderr, "Usage: %s <output file>\n", argv[0]);
return 0;
}
filename = argv[1];
/* find the MP2 encoder */ //查找音频编码器
codec = avcodec_find_encoder(AV_CODEC_ID_MP2);
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
//分配 音频编码器上下文
c = avcodec_alloc_context3(codec);
if (!c) {
fprintf(stderr, "Could not allocate audio codec context\n");
exit(1);
}
/* put sample parameters */
c->bit_rate = 64000; //设置码率64kb
/* check that the encoder supports s16 pcm input */
c->sample_fmt = AV_SAMPLE_FMT_S16; //采样位数 16位
if (!check_sample_fmt(codec, c->sample_fmt)) {//看编码器是否支持这个采样位数
fprintf(stderr, "Encoder does not support sample format %s",
av_get_sample_fmt_name(c->sample_fmt));
exit(1);
}
/* select other audio parameters supported by the encoder */
c->sample_rate = select_sample_rate(codec);//找到最佳采样率.向上靠拢
c->channel_layout = select_channel_layout(codec); //找到最高的channel_layout
//编码器用户设置channel_layout, 解码器由文件里得到
c->channels = av_get_channel_layout_nb_channels(c->channel_layout); //找到最多的通道,根据channel_layout
/* open it */ //初始化编码器上下文
if (avcodec_open2(c, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
//打开文件
f = fopen(filename, "wb");
if (!f) {
fprintf(stderr, "Could not open %s\n", filename);
exit(1);
}
//初始化packet
/* packet for holding encoded output */
pkt = av_packet_alloc();
if (!pkt) {
fprintf(stderr, "could not allocate the packet\n");
exit(1);
}
//初始化frame
/* frame containing input raw audio */
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate audio frame\n");
exit(1);
}
//用编码器上下文 来更新frame. 因为编码是把原始数据转为压缩数据.所以这些参数都是有用户指定的.
frame->nb_samples = c->frame_size;//帧大小
frame->format = c->sample_fmt;
frame->channel_layout = c->channel_layout;
/* allocate the data buffers */ //给frame里的buf 和bufsize 分配控件
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate audio data buffers\n");
exit(1);
}
/* encode a single tone sound */
t = 0;
tincr = 2 * M_PI * 440.0 / c->sample_rate;//这个看不懂
for (i = 0; i < 200; i++) {
/* make sure the frame is writable -- makes a copy if the encoder
* kept a reference internally */
//确保frame 里的缓存是可写入的.如果不可写入.就分配新的缓存给frame.这是防止编码器群对frame处理还没问问你的,新数据又无法写入
ret = av_frame_make_writable(frame);
if (ret < 0)
exit(1);
//这一段看不懂,难道是随机产生的音乐数据?
samples = (uint16_t *) frame->data[0];
for (j = 0; j < c->frame_size; j++) {
samples[2 * j] = (int) (sin(t) * 10000);
for (k = 1; k < c->channels; k++)
samples[2 * j + k] = samples[2 * j];
t += tincr;
}
//编码数据, 参数是编码上下文.帧.packet.文件 .应该是把帧数据编码后得到packet.在写入问就
encode(c, frame, pkt, f);
}
//刷新编码器里最后的数据,老操作
/* flush the encoder */
encode(c, NULL, pkt, f);
fclose(f);
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&c);
return 0;
}