您当前的位置: 首页 > 学无止境 > 心得笔记 网站首页心得笔记
4. swr_init音频重采样上下文初始化和swr_convert音频重采样代码示例~1
发布时间:2021-06-02 17:36:44编辑:雪饮阅读()
音頻重采樣的相關函數依賴于libswresample.so,依賴於libswresample/swresample.h頭文件。
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.10.2)
# Declares and names the project.
project("ndk_and_41")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#添加頭文件路徑(括號中的include是相對於本文件路徑)
include_directories(../../../include)
#設置ffmpeg庫所在路徑的變量,這裏的FF是自定義的一個名字
set(FF ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI})
#avcodec這個是自定義的一個名字
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${FF}/libavcodec.so)
add_library(avformat SHARED IMPORTED)
set_target_properties(avformat PROPERTIES IMPORTED_LOCATION ${FF}/libavformat.so)
add_library(avutil SHARED IMPORTED)
set_target_properties(avutil PROPERTIES IMPORTED_LOCATION ${FF}/libavutil.so)
add_library(swscale SHARED IMPORTED)
set_target_properties(swscale PROPERTIES IMPORTED_LOCATION ${FF}/libswscale.so)
add_library(swresample SHARED IMPORTED)
set_target_properties(swresample PROPERTIES IMPORTED_LOCATION ${FF}/libswresample.so)
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
avcodec avformat avutil swscale swresample
#這裏將avcodec庫鏈接進來
# Links the target library to the log library
# included in the NDK.
${log-lib} )
那麽最後就是這次的主要實現cpp/native-lib.cpp:
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.ndk_and_41"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
ndk{
abiFilters "armeabi-v7a"
abiFilters "arm64-v8a"
}
}
sourceSets{
main{
jniLibs.srcDirs=['libs']
}
}
}
packagingOptions {
pickFirst 'lib/armeabi-v7a/libavcodec.so'
pickFirst 'lib/armeabi-v7a/libavutil.so'
pickFirst 'lib/armeabi-v7a/libavformat.so'
pickFirst 'lib/arm64-v8a/libavcodec.so'
pickFirst 'lib/arm64-v8a/libavutil.so'
pickFirst 'lib/arm64-v8a/libavformat.so'
pickFirst 'lib/arm64-v8a/libswscale.so'
pickFirst 'lib/arm64-v8a/libswresample.so'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
#include <string>
#include <android/log.h>
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,"testff",__VA_ARGS__)
extern "C"{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavcodec/jni.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
static double r2d(AVRational r){
return r.num==0 | r.den==0 ? 0:(double) r.num/(double)r.den;
}
/*
* 封裝一個當前時間戳返回的函數,以100小時以内返回
* ms:一般用於表示毫秒的單位
* */
long long GetNowMs(){
struct timeval tv;
gettimeofday(&tv,NULL);
/*
gettimeofday是计算机函数,使用C语言编写程序需要获得当前精确时间(1970年1月1日到现在的时间),或者为执行计时,可以使用gettimeofday()函数。
該函數返回的時間數據不是實時的,是根據cpu跳數來定的
tv_sec 代表多少秒,所以這裏tv_sec 代表多少秒從1970年1月1日到現在的秒數,因爲本函數最後要返回的是毫秒,這裏防止數據過大,所以這裏只返回100小時内的秒數
*/
int sec=tv.tv_sec%360000;
//tv.tv_usec:微秒,1毫秒等於100微秒
long long t=sec*1000+tv.tv_usec/100;
return t;
}
extern "C"
JNIEXPORT
jint JNI_OnLoad(JavaVM *vm,void *res)
{
//JNI_OnLoad函數,java加載時候自動調用,這裏用做給c中調用java時的環境變量支持
av_jni_set_java_vm(vm,0);
//這裏返回一個java版本,這裏假定使用1.4的java版本
return JNI_VERSION_1_4;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndk_1and_141_MainActivity_stringFromJNI(JNIEnv* env,jobject) {
std::string hello = "Hello from C++";
hello+=avcodec_configuration();
//初始化解封裝
av_register_all();
//初始化網絡
avformat_network_init();
/*
* avcodec_register_all()只有调用了该函数,才能使用编解码器等。
* */
avcodec_register_all();
AVFormatContext *ic=NULL;
//要取一些信息,mp4格式取的比較多
char path[]="/sdcard/1080.mp4";
int re=avformat_open_input(&ic,path,0,0);
if(re!=0){
LOGW("avformat_open_input failed!:%s",av_err2str(re));
return env->NewStringUTF(hello.c_str());
}
/*
這裏需要漲點教訓,原來這裏是LOGW("avformat_open_input %s success!");
也就是多帶了一個%s,很明顯這裏語法在c中是有問題的,可是編譯過程時候沒有報錯。
日志貓中報錯信息讓我一度懷疑自己的arm64位動態鏈接庫編譯錯了呢。。。
*/
LOGW("avformat_open_input success!");
/*
duration方法:
mp4格式可以直接獲取到時長
返回類型是int64_t:
int64_t 是标准C ++类型,用于完全64位的有符号整数。 int64 不是标准类型。
第一个C ++标准没有固定宽度类型。在将 int64_t 添加到标准C ++之前,不同的编译器都实现了64位类型,但它们使用了自己的名称(例如 long long < / code>, __ int64 等。)
那麽因爲long long < / code>,所以這裏int64_t的輸出就是l ld了
*/
//對於flv來説,要先探測獲取下流信息,若不先探測,則獲取的場地可能就是如:-9223372036854775808一樣的負數
re=avformat_find_stream_info(ic,0);
if(re!=0){
LOGW("avformat_find_stream_info failed!");
}
//這裏格式化int64_t的時候千萬記住不是%lld而是%l ld
LOGW("duration=%l ld nb_streams=%d",ic->duration,ic->nb_streams);
int fps = 0;
int videoStream = 0;
int audioStream = 1;
/*
遍历获取AVStream音视频流信息并打印参数
*/
for(int i = 0; i < ic->nb_streams; i++)
{
AVStream *as = ic->streams[i];
if(as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
LOGW("视频数据");
videoStream = i;
//avg_frame_rate:幀率,一個分數,就是fps的分數表示法,分數表示可以有效保護精度損失
fps = r2d(as->avg_frame_rate);
/*
codecpar->width,codecpar->height:视频的宽高,只有视频有
codecpar->codec_id: 獲取解碼器的id號
codecpar->format:格式。对于视频来说指的就是像素格式(YUV420,YUV422...),对于音频来说,指的就是音频的采样格式。
*/
LOGW("fps = %d,width=%d height=%d codeid=%d pixformat=%d",fps,
as->codecpar->width,
as->codecpar->height,
as->codecpar->codec_id,
as->codecpar->format
);
}
else if(as->codecpar->codec_type ==AVMEDIA_TYPE_AUDIO )
{
LOGW("音频数据");
audioStream = i;
/*
codecpar->sample_rate:樣本率
codecpar->channels:声道数
codecpar->format:格式。对于视频来说指的就是像素格式(YUV420,YUV422...),对于音频来说,指的就是音频的采样格式。
*/
LOGW("sample_rate=%d channels=%d sample_format=%d",
as->codecpar->sample_rate,
as->codecpar->channels,
as->codecpar->format
);
}
}
/*
獲取音頻流信息
第一個參數:AVStream的實例
第二個參數type:音頻或視頻流類型
第三個參數wanted_stream_nb是指定索引,這裏沒有必要,所以就為-1
第四個參數related_stream,同一節目組的相關流信息,這裏也沒有節目組,所以也就設置為-1
第五個參數decoder_ret 這個參數的作用我們要單獨做
第五個參數flags 目前還沒有用
*/
audioStream=av_find_best_stream(ic,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
LOGW("av_find_best_stream audioStream=%d",audioStream);
//软解码器-視頻解碼器
//avcodec_find_decoder:获取解码器
AVCodec *codec=avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id);
//硬解码,avcodec_find_decoder_by_name通过名字打开解码器,比如這裏h264_mediacodec就是用Android里面自带的解码模块
codec=avcodec_find_decoder_by_name("h264_mediacodec");
if(!codec){
LOGW("avcodec_find failed!");
return env->NewStringUTF(hello.c_str());
}
//解码器初始化,avcodec_alloc_context3方法用于获取编解码器上下文信息
AVCodecContext *vc=avcodec_alloc_context3(codec);
//avcodec_parameters_to_context()方法用于将流的参数(stream->codecpar)复制到解码器中,否则某些流可能无法正常解码。
avcodec_parameters_to_context(vc,ic->streams[videoStream]->codecpar);
//编解码时的线程数量,由用户设置,与CPU核心数有关,最佳效果一般设置为CPU核心数*2.
vc->thread_count=1;
//打开解码器
/*
* avcodec_open2()各个参数的含义:
avctx:需要初始化的AVCodecContext。
codec:输入的AVCodec
options:一些选项。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置
这里用不到codec和options,所以就填写0吧
* */
re=avcodec_open2(vc,0,0);
if(re!=0){
LOGW("avcodec_open2 video failed!");
return env->NewStringUTF(hello.c_str());
}
//软解码器-音頻解碼器
//avcodec_find_decoder:获取解码器
AVCodec *acodec=avcodec_find_decoder(ic->streams[audioStream]->codecpar->codec_id);
//硬解码(先看软解码,硬解码另外再做)
//codec=avcodec_find_decoder_by_name("h264_mediacodec");
if(!acodec){
LOGW("avcodec_find_audio failed!");
return env->NewStringUTF(hello.c_str());
}
//解码器初始化,avcodec_alloc_context3方法用于获取编解码器上下文信息
AVCodecContext *ac=avcodec_alloc_context3(acodec);
//avcodec_parameters_to_context()方法用于将流的参数(stream->codecpar)复制到解码器中,否则某些流可能无法正常解码。
avcodec_parameters_to_context(ac,ic->streams[audioStream]->codecpar);
//编解码时的线程数量,由用户设置,与CPU核心数有关,最佳效果一般设置为CPU核心数*2.
ac->thread_count=1;
//打开解码器
/*
* avcodec_open2()各个参数的含义:
avctx:需要初始化的AVCodecContext。
codec:输入的AVCodec
options:一些选项。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置
这里用不到codec和options,所以就填写0吧
* */
re=avcodec_open2(ac,0,0);
if(re!=0){
LOGW("avcodec_open2 audio failed!");
return env->NewStringUTF(hello.c_str());
}
/*
讀取幀數據
av_packet_alloc():分配一个结构体大小的内存
av_read_frame():获取视频的一帧,不存在半帧说法。但可以获取音频的若干帧。
av_seek_frame():
函數原型:
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp,int flags);
参数说明:
s:操作上下文;
stream_index:基本流索引,表示当前的seek是针对哪个基本流,比如视频或者音频等等。
timestamp:要seek的时间点,以time_base或者AV_TIME_BASE为单位。單位毫秒。
Flags:seek标志,可以设置为按字节,在按时间seek时取该点之前还是之后的关键帧,以及不按关键帧seek等
*/
AVPacket *pkt=av_packet_alloc();
/*
av_frame_alloc:分配AVFrame并将其字段设置为默认值。主要该函数只分配AVFrame的空间,它的data字段的指定的buffer需要其它函数分配。
*/
AVFrame *frame = av_frame_alloc();
long long start=GetNowMs();
int frameCount=0;
//初始化像素格式转换的上下文
SwsContext *vctx = NULL;
//outWidth、outHeight用於在界面上顯示
int outWidth = 1280;
int outHeight = 720;
//這裏不是多維數組而是1920*1080*4個長度單位的數組,足夠即可,超過也無所謂,一個像素等於4個字節
char *rgb = new char[1920*1080*4];
//音频重采样上下文初始化
//此函数用于申请一个SwrContext重采樣结构体
SwrContext *actx = swr_alloc();
/*
swr_alloc_set_opts:用於设置SwrContext的参数
参数1:重采样上下文
参数2:输出的layout, 如:5.1声道…
参数3:输出的样本格式。Float, S16, S24
参数4:输出的样本率。可以不变。變了容易出現一些音頻的問題。
参数5:输入的layout。
参数6:输入的样本格式。
参数7:输入的样本率。
参数8,参数9,都是與日志有關,不用管,可直接传0
针对音频的播放速度,可以通过样本率的改变而改变。
*/
/*
av_get_default_channel_layout(2)
原來是av_get_default_channel_layout(ac->channels),輸出layout假定是不變的和原來一致的
當然也可以固定的只輸出2個聲道,多聲道,聲卡也不一定支持
AV_SAMPLE_FMT_S16:這個格式基本上手機都能播放,手機都支持這個格式,基本上所有的聲卡都能播放
*/
actx = swr_alloc_set_opts(actx,
av_get_default_channel_layout(2),
AV_SAMPLE_FMT_S16,
ac->sample_rate,
av_get_default_channel_layout(ac->channels),
ac->sample_fmt,ac->sample_rate,
0,0 );
//正式初始化音頻采樣上下文
re = swr_init(actx);
//雙通道,一秒鈡的音頻
char *pcm = new char[48000*4*2];
if(re != 0)
{
LOGW("swr_init failed!");
}
else
{
LOGW("swr_init success!");
}
for(;;){
//假定每3秒統計下
if(GetNowMs()-start>=3000){
LOGW("now decode fps is %d",frameCount/3);
}
int re=av_read_frame(ic,pkt);
if(re!=0){
LOGW("讀取到結尾処!");
/*
這裏跳轉到第20秒処
AVSEEK_FLAG_BACKWARD表示所跳的時間點如果沒有幀则向前找第一个:若你设置seek时间为1秒,但是只有0秒和2秒上才有I帧,则时间从0秒开始。
AVSEEK_FLAG_FRAME:若你设置seek时间为1秒,但是只有0秒和2秒上才有I帧,则时间从2秒开始。是基于帧数量快进.
AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME:既往後找,又找關鍵幀
*/
int pos=20*r2d(ic->streams[videoStream]->time_base);
av_seek_frame(ic,videoStream,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME);
continue;
}
// LOGW("stream =%d size=%d pts=%l ld flag=%d",pkt->stream_index,pkt->size,pkt->pts,pkt->flags);
/*
av_packet_unref():释放空间,减少引用计数:
对AVPacket缓冲区的引用计数-1.(内部调用 atomic_fetch_add_explicit(…, -1, …)),如果引用为个数为1,将释放data缓冲区;
将其余信息包字段重置为它们的默认值。
例如在编码时,循环处理压缩数据,每循环一次都会分配AVPacket->data来存储压缩后的数据(如 avcodec_receive_packet,内部使用 av_new_packet ),
处理完压缩数据之后,并且在进入下一次循环之前,记得使用 av_packet_unref 来释放已经分配的AVPacket->data缓冲区。
*/
//av_packet_unref(pkt);
//判斷流進行不同的解碼器上下文初始化
AVCodecContext *cc = vc;
if(pkt->stream_index == audioStream){
cc=ac;
}
//发送到线程中解码,发送数据到ffmepg,放到解码队列中
re = avcodec_send_packet(cc,pkt);
//清理
int p = pkt->pts;
//avpacket 解码和解封装是2个线程,如果解封装后,调用此函数后,会将avpacket的引用计数加1 或者复制一份(没有计数引用)。因此在调用了后,释放掉 avpacket。
av_packet_unref(pkt);
if(re != 0)
{
LOGW("avcodec_send_packet failed!");
continue;
}
//不斷的從當前解碼器上下文中解碼數據到解碼后用於存放AVPacket解碼后數據的數據結構AVFrame
for(;;)
{
re = avcodec_receive_frame(cc,frame);
if(re !=0)
{
//LOGW("avcodec_receive_frame failed!");
break;
}
//%lld:即long long=64bit。
LOGW("avcodec_receive_frame %lld",frame->pts);
//如果是視頻幀就統計一次
if(cc==vc){
frameCount++;
/*
sws_getCachedContext:会根据传入的上下文到缓冲里面去找
第一参数可以传NULL,默认会开辟一块新的空间。
srcW,srcH, srcFormat,原始数据的宽高和原始像素格式(YUV420),
dstW,dstH,dstFormat; 目标宽,目标高,目标的像素格式(这里的宽高可能是手机屏幕分辨率,RGBA8888),这里不仅仅包含了尺寸的转换和像素格式的转换
flag :
提供了一系列的算法,快速线性,差值,矩阵,不同的算法性能也不同,快速线性算法性能相对较高。只针对尺寸的变换。对像素格式转换无此问题
SWS_FAST_BILINEAR:图像无明显失真,感觉效果很不错
sws_getCachedContext最後三個這裏不使用,null和0是等同的
*/
vctx = sws_getCachedContext(vctx,
frame->width,
frame->height,
(AVPixelFormat)frame->format,
outWidth,
outHeight,
AV_PIX_FMT_RGBA,
SWS_FAST_BILINEAR,
0,0,0
);
if(!vctx)
{
LOGW("sws_getCachedContext failed!");
}
else
{
//解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)
uint8_t *data[AV_NUM_DATA_POINTERS] = {0};
//unsigned char 是8位, uint8_t是8位,一般理解就是它们没什么区别,用 uint8_t更加健壮
data[0] =(uint8_t *)rgb;
//sws_scale最後兩個個參數定义输出图像信息(输出的每个颜色通道数据指针,每个颜色通道行字节数)
int lines[AV_NUM_DATA_POINTERS] = {0};
//RGBA固定的是4,根據上面sws_getCachedContext傳入的參數像素格式dstFormat,這裏是AV_PIX_FMT_RGBA就是固定為4
lines[0] = outWidth * 4;
//sws_scale返回值輸出數據高度,若為0就失敗
int h = sws_scale(vctx,
(const uint8_t **)frame->data,
frame->linesize,0,
frame->height,
data,lines);
LOGW("sws_scale = %d",h);
}
}
//音频重采樣
else
{
//兩個通道,交錯存在一個地址上,那麽長度只需要2個即可,因爲char類型的'\0'單獨占一位,上面的pcm是一個字符數組/字符串
uint8_t *out[2] = {0};
out[0] = (uint8_t*) pcm;
//音频重采样
/*
swr_convert()
针对每一帧音频的处理。把一帧帧的音频作相应的重采样
int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in, int in_count);
参数1:音频重采样的上下文
参数2:输出的指针。传递的输出的数组
参数3:输出的样本数量,不是字节数。单通道的样本数量。
参数4:输入的数组,AVFrame解码出来的DATA
参数5:输入的单通道的样本数量。
*/
int len = swr_convert(actx,out,
frame->nb_samples,
(const uint8_t**)frame->data,
frame->nb_samples);
LOGW("swr_convert = %d",len);
}
}
}
delete rgb;
delete pcm;
avformat_close_input(&ic);
return env->NewStringUTF(hello.c_str());
}
关键字词:swr_init,swr_convert
相关文章
-
无相关信息