音视频开发8. 使用ffmpeg 将pcm转码aac实践(C++)

Posted 编程圈子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了音视频开发8. 使用ffmpeg 将pcm转码aac实践(C++)相关的知识,希望对你有一定的参考价值。

@[TOC](音视频开发8. 使用ffmpeg 将pcm转码aac实践(C++))

一、 准备环境

  • CentOS 已安装ffmpeg库
  • 本地使用vscode,安装 Remote developement 远程开发插件 。
  • 实现目标: 将pcm文件转码aac格式文件。

PCM全称Pulse-Code Modulation,即脉冲调制编码。PCM音频数据是未经压缩的音频采样数据裸流,一般播放器无法直接播放。如果要播放PCM还需要描述其采样率、声道、采样格式、码率等信息。

本文示例需要准备一段PCM样本文件,本文使用的PCM:

  • 采样率:44100
  • 双声道
  • 码率:64000
  • 样本格式:float, planar
  • 立体声

二、 项目结构

三、 audio.cpp 完整代码

1. 引用ffmpeg头

#include <stdio.h>
#include <iostream>

#ifdef __cplusplus
extern "C"

#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
#include "libavutil/log.h"
#include "libswresample/swresample.h"
#ifdef __cplusplus

#endif
using namespace std;

2. 主体代码

int main()
  av_register_all();
  avcodec_register_all();

  char inputfile[] = "input.pcm";
  char outputfile[] = "audio.aac";

  AVCodec* avCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
  if(!avCodec)
    cout << "avcodec_find_encoder failed" << endl;
    return -1;
  
  // 编码器上下文配置
  AVCodecContext* avCodecContext = avcodec_alloc_context3(avCodec);
  if(!avCodecContext)
    cout << "avcodec_alloc_context3 failed" << endl;
    return -1;
  
  int ret=0;
  // 采样率设置
  avCodecContext->sample_rate = 44100;
  // 双通道
  avCodecContext->channels = 2; 
  // 32位样本格式
  avCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
  // 码率
  avCodecContext->bit_rate = 64000;
  // 声道类型
  avCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
  // 音频帧使用公共头部
  avCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  // 打开音频的编码器
  ret = avcodec_open2(avCodecContext, avCodec,NULL);
  if(ret<0)
    cout << "avcodec_open2 failed" << endl;
    return -1;
  
  // 创建输出的上下文
  AVFormatContext* avFormatContext = NULL;
  // 初始化
  avformat_alloc_output_context2(&avFormatContext, NULL, NULL, outputfile);
  if(!avFormatContext)
    cout << "avformat_alloc_output_context2 failed" << endl;
    return -1;
  
  cout << "创建音频流,然后打印输出流信息" << endl;
  // 创建音频流
  AVStream* st = avformat_new_stream(avFormatContext, NULL);
  // 音频流参数设置
  st->codecpar->codec_tag = 0;
  // 编码器参数复制到音频流上,省得再设置一遍
  avcodec_parameters_from_context(st->codecpar, avCodecContext);
  // 第三个参数,0音频、1视频
  av_dump_format(avFormatContext, 0, outputfile, 1);

  cout << "打开输出文件 " << endl;
  // 打开输出文件
  ret = avio_open(&avFormatContext->pb, outputfile, AVIO_FLAG_WRITE);
  if(ret<0)
    cout << "avio_open failed" << endl;
    return -1;
  
  cout << "写文件头部,创建输出文件" << endl;
  // 写头部信息,创建输出文件
  avformat_write_header(avFormatContext, NULL);
  cout << "重采样上下文" << endl;
  // 重采样上下文
  SwrContext* swrContext = NULL;

  // 设置参数, 1. 上下文 2.输出声道布局 4.输出采样率, 5.输入声道布局 6.输入样本格式 7.输入采样率 8.配音 9.日志
  swrContext = swr_alloc_set_opts(swrContext, avCodecContext->channel_layout, avCodecContext->sample_fmt, avCodecContext->sample_rate,
    AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, 44100,
    0, 0);
  if(!swrContext)
    cout << "swr_alloc_set_opts failed" << endl;
    return -1;
  
  // 初始化
  ret = swr_init(swrContext);
  if(ret<0)
    cout << "swr_init failed" << endl;
    return -1;
  
  // 一帧帧读取 PCM 数据、重采样、编码、写入.1帧是1024个样本。
  AVFrame* avFrame = av_frame_alloc();
  // 样本格式 float4字节
  avFrame->format = AV_SAMPLE_FMT_FLTP;
  // 通道数
  avFrame->channels=2;
  // 通道布局
  avFrame->channel_layout = AV_CH_LAYOUT_STEREO;
  // 每一帧音频的样本数量
  avFrame->nb_samples=1024;

  ret = av_frame_get_buffer(avFrame, 0);
  if(ret<0)
    cout << "av_frame_get_buffer failed" << endl;
    return -1;
  
  // 准备缓存空间存放数据
  int readSize = avFrame->nb_samples*2*2; //双声道,float
  char* pcms = new char[readSize];
  cout << "打开输入文件 每次读取(双声道,float):readsize=" << readSize << endl;
  // 打开输入文件 
  FILE* fp = fopen(inputfile, "rb");
  cout << "==========循环读入帧==========" << endl;
  int sum=0;
  int index=0;
  for(;;)
    // 存放编码后的数据
    AVPacket pkt;
    // 包初始化
    av_init_packet(&pkt);
    int len = fread(pcms,1,readSize, fp);
    if(len <=0)
      // 文件读完了
      cout << "文件读取结束" << endl;
      // 告诉解码器没有帧了
      avcodec_send_frame(avCodecContext, NULL);
      while(avcodec_receive_packet(avCodecContext, &pkt)!=AVERROR_EOF);
      break;
    else
      index ++;
      sum += len;
      // 强制数据类型转换,重采样之前的数据
      const uint8_t* data[1];
      data[0] = (uint8_t*)pcms;
      // 重采样 1.重采样上下文 2.重采样后的数据 3.样本数量不变 4.重采样之前的数据
      len = swr_convert(swrContext, avFrame->data, avFrame->nb_samples, data, avFrame->nb_samples);
      if(len <=0)
        cout << "重采样输出异常" << len << endl;
        break;
      
      // ------------对重采样的数据重新编码--------
      // 编码重采样后的数据

      // 重采样的数据发送到编码线程
      // 1. 编码器上下文 2.要发送的数据
      ret = avcodec_send_frame(avCodecContext, avFrame);
      if(ret < 0)
        // 完成发送了
        cout << "发编码器线程异常" << ret << endl;
        continue;
      
      
      // 接收编码和解码后的数据
      
      // 取出编码后的数据,TODO: 这里要加循环多次读取以确保读取成功
      ret = avcodec_receive_packet(avCodecContext, &pkt);

      if(ret == -EAGAIN)
        cout << "缓冲区未满 等待新数据" << endl;
       else if(ret != 0)
        cout << "取编码结果异常" << ret << endl;
        continue;
      
      // 指定是音频流。 1为视频流
      pkt.stream_index = 0;
      // 解码时间、显示时间置为0
      pkt.dts = 0;
      pkt.pts = 0;
      // 编码后的写入ac文件 1.输出上下文
      av_interleaved_write_frame(avFormatContext, &pkt);
      if(index%50==0)
        cout << "第" << index << "帧数据:" << len << "..." << "已处理 " << sum << " 字节" << endl ; 
      
    
  
  cout << endl << "处理总字节数:" << sum << endl;
  cout << endl << "释放资源" <<endl;
  // 释放
  delete pcms;
  pcms = NULL;
  
  // 写入索引
  av_write_trailer(avFormatContext);

  fclose(fp);

  cout << "关闭输出文件" << endl;
  avio_close(avFormatContext->pb);
  cout << "关闭编码器" << endl;
  avcodec_close(avCodecContext);
  cout << "清理编码器参数" << endl;
  avcodec_free_context(&avCodecContext);
  cout << "清理输出上下文" << endl;
  avformat_free_context(avFormatContext);
  return 0;



CMakeLists.txt

cmake_minimum_required(VERSION 3.17)
project(ffmpeg_demo)

# 设置ffmpeg依赖库及头文件所在目录,并存进指定变量
set(ffmpeg_libs_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)
set(ffmpeg_headers_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)

#对于find_package找不到的外部依赖库,可以用add_library添加
# SHARED表示添加的是动态库
# IMPORTED表示是引入已经存在的动态库

add_library( avcodec SHARED IMPORTED)
add_library( avfilter SHARED IMPORTED )
add_library( swresample SHARED IMPORTED )
add_library( swscale SHARED IMPORTED )
add_library( avformat SHARED IMPORTED )
add_library( avutil SHARED IMPORTED )


#指定所添加依赖库的导入路径
set_target_properties( avcodec PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavcodec/libavcodec.so )
set_target_properties( avfilter PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavfilter/libavfilter.so )
set_target_properties( swresample PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libswresample/libswresample.so )
set_target_properties( swscale PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libswscale/libswscale.so )
set_target_properties( avformat PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavformat/libavformat.so )
set_target_properties( avutil PROPERTIES IMPORTED_LOCATION $ffmpeg_libs_DIR/libavutil/libavutil.so )


# 添加头文件路径到编译器的头文件搜索路径下,多个路径以空格分隔
include_directories( $ffmpeg_headers_DIR )
link_directories($ffmpeg_libs_DIR )
link_directories(/usr/lib)


set(CMAKE_CXX_STANDARD 14)
# add_executable(ffmpeg_demo main.cpp)
add_executable(ffmpeg_demo audio.cpp)
target_link_libraries($PROJECT_NAME  avcodec avformat avutil swresample swscale swscale avfilter )

运行效果:

以上是关于音视频开发8. 使用ffmpeg 将pcm转码aac实践(C++)的主要内容,如果未能解决你的问题,请参考以下文章

音视频开发9. 使用ffmpeg 将pcm转码mp3实践(C++)

音视频开发9. 使用ffmpeg 将pcm转码mp3实践(C++)

ffmpeg 音频转码

Qt-FFmpeg开发-音频解码为PCM文件

使用 ffmpeg 转码和分段

【FFMPEG做视频转码】