#include "hjcommon.hpp"
#include <android/native_window.h>
#include <android/native_window_jni.h>
static jobject obj_audiotrack; // AudioTrack 对象,全局引用
static jmethodID mid_write; // jmethodID 不用做成全局引用也能跨线程使用
static jmethodID mid_release; // AudioTrack 释放函数
static void initAudioTrack(JNIEnv *env, jobject instance)
{
jclass clz_mp6 = env->GetObjectClass(instance);
jmethodID mid_create = env->GetMethodID(clz_mp6, "createAudioTrack", "(II)Landroid/media/AudioTrack;"); // 获取创建AudioTrack实例的java函数
jobject obj_aud = env->CallObjectMethod(instance, mid_create, 44100, 2); // 传参 44100, 2 是因为我事先知道其采样率与双通道
jclass clz_aud = env->GetObjectClass(obj_aud);
jmethodID mid_play = env->GetMethodID(clz_aud, "play", "()V"); // 获取AudioTrack的play函数
env->CallVoidMethod(obj_aud, mid_play); // 调用play函数
mid_write = env->GetMethodID(clz_aud, "write", "([BII)I"); // 获取AudioTrack的write函数
mid_release = env->GetMethodID(clz_aud, "release", "()V");
obj_audiotrack = env->NewGlobalRef(obj_aud);
}
static void audiotrack_write(JNIEnv *env, uint8_t *out_buffer, int out_buffer_size) // 往 AudioTrack 中写入数据,播放音频
{
//out_buffer缓冲区数据,转成byte数组
jbyteArray audio_sample_array = env->NewByteArray(out_buffer_size);
jbyte* sample_bytep = env->GetByteArrayElements(audio_sample_array, NULL);
//out_buffer的数据复制到sampe_bytep
memcpy(sample_bytep, out_buffer, out_buffer_size);
//同步
env->ReleaseByteArrayElements(audio_sample_array, sample_bytep, 0);
//AudioTrack.write PCM数据
env->CallIntMethod(obj_audiotrack, mid_write, audio_sample_array, 0, out_buffer_size);
//释放局部引用
env->DeleteLocalRef(audio_sample_array);
}
JNIEXPORT void JNICALL Java_hankin_hjmedia_mpeg_some_Mp6_11Activity_resample(JNIEnv *env, jobject instance, jstring url_, jobject surface)
{
const char * path = env->GetStringUTFChars(url_, NULL);
if (path[0]=='\0')
{
LOGE("path is empty.");
return;
}
initAudioTrack(env, instance); // 初始化 AudioTrack
// av_register_all();
int netInit = avformat_network_init();
if (netInit!=0) LOGW("avformat_network_init is failed.");
// avcodec_register_all();
AVFormatContext * formatContext = NULL;
int openRet = avformat_open_input(&formatContext, path, NULL, NULL);
if (openRet!=0 || formatContext==NULL)
{
LOGE("avformat_open_input is failed.");
return;
}
env->ReleaseStringUTFChars(url_, path);
if (formatContext->duration<=0) avformat_find_stream_info(formatContext, NULL);
LOGD("formatContext->duration=%lld, formatContext->bit_rate=%lld", formatContext->duration, formatContext->bit_rate);
int videoStream = av_find_best_stream(formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
int audioStream = av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (videoStream==AVERROR_STREAM_NOT_FOUND || audioStream==AVERROR_STREAM_NOT_FOUND)
{
LOGE("video stream or audio stream not found.");
return;
}
LOGD("vidoe->width=%d, video->height=%d, audio->sample_rate=%d", formatContext->streams[videoStream]->codecpar->width,
formatContext->streams[videoStream]->codecpar->height, formatContext->streams[audioStream]->codecpar->sample_rate);
AVCodecContext * videoContext = avcodec_alloc_context3(NULL);
int gvRet = hjgetAVDecoder6_1(videoContext, formatContext->streams[videoStream]->codecpar, true, false);
if (gvRet!=0) return;
AVCodecContext * audioContext = avcodec_alloc_context3(NULL);
int gaRet = hjgetAVDecoder6_1(audioContext, formatContext->streams[audioStream]->codecpar, false);
if (gaRet!=0) return;
AVPacket * pkt = av_packet_alloc();
AVFrame * frame = av_frame_alloc();
long long start = hjgetNowMs();
int frameCount = 0;
// 初始化视频像素格式转换的上下文
SwsContext * swsContext = NULL;
int outWidth = 1280; // 转换后的宽高
int outHeight = 720;
unsigned char * rgba = new unsigned char[1920*1080*4]; // 创建缓存,用做像素格式转换
// 音频解码出来后时无法直接播放的,需要重采样
SwrContext * swrContext = swr_alloc(); // 创建音频重采样上下文
/*
struct SwrContext *swr_alloc_set_opts( // 设置音频重采样上下文参数
struct SwrContext *s, // 上下文
int64_t out_ch_layout, // 输出的声道
enum AVSampleFormat out_sample_fmt, // 输出的样本格式
int out_sample_rate, // 输出的样本率,要改变播放速度可以通过改变此输出的样本率来改变,不过可能会失帧
int64_t in_ch_layout, // 输入的声道
enum AVSampleFormat in_sample_fmt, // 输入的样本格式
int in_sample_rate, // 输入的样本率
int log_offset, // 日志,传0
void *log_ctx // 日志,传0
);
*/ // av_get_default_channel_layout 根据给的声道数返回默认的channel layout ,固定让输出2声道 AV_SAMPLE_FMT_S16 样本格式
swr_alloc_set_opts(swrContext, av_get_default_channel_layout(2), AV_SAMPLE_FMT_S16, audioContext->sample_rate,
av_get_default_channel_layout(audioContext->channels), audioContext->sample_fmt, audioContext->sample_rate, 0, 0);
int swrRet = swr_init(swrContext); // 初始化,返回 0 ok
if (swrRet!=0)
{
LOGE("swr_init is failed : %s", av_err2str(swrRet));
return;
}
unsigned char * pcm = new unsigned char[48000*4*2]; // 重采样时样本数量的缓存,设大点无所谓,但是不要小了
// NDK中使用java传递进来的surface创建native窗口,同其他对象一样,记得用完后调用ANativeWindow_release()将引用计数减1 。 代码在 libandroid.so 中
ANativeWindow * nwin = ANativeWindow_fromSurface(env, surface);
if (nwin==NULL)
{
LOGE("ANativeWindow_fromSurface failed.");
return;
}
// 设置窗口的大小、格式,要与视频转换后的像素格式匹配,显示时,native窗口会自动拉伸铺满SurfaceView的大小。 return 0 for success
int wRet = ANativeWindow_setBuffersGeometry(nwin, outWidth, outHeight, WINDOW_FORMAT_RGBA_8888);
if (wRet!=0)
{
LOGE("ANativeWindow_setBuffersGeometry is failed.");
return;
}
ANativeWindow_Buffer wbuf; // surface的双缓冲,内存与显卡内存交换内存的地方
bool isGo = true;
while (isGo)
{
if (hjgetNowMs()-start >= 3000)
{
LOGW("3秒内平均每秒解码视频帧数 : %d", frameCount/3);
start = hjgetNowMs();
frameCount = 0;
}
int pRet = av_read_frame(formatContext, pkt);
if (pRet!=0)
{
LOGI("av_read_frame end.");
break;
}
LOGV("packet stream_index=%d, pts=%lld, dts=%lld, size=%d", pkt->stream_index, pkt->pts, pkt->dts, pkt->size);
AVCodecContext * cc = pkt->stream_index==videoStream ? videoContext : audioContext;
int spRet = avcodec_send_packet(cc, pkt);
if (cc==videoContext) LOGV("video avcodec_send_packet=%d", spRet); else LOGV("audio avcodec_send_packet=%d", spRet);
bool is = true;
while (is)
{
int rpRet = avcodec_receive_frame(cc, frame);
// Nexus手机上使用硬解码,解出的视频的格式为 AV_PIX_FMT_NV12 ,不同解码方式,不同设备上最后解出的格式可能不一样
if (rpRet==0) if (cc==videoContext) LOGV("video frame pts=%lld, format=%d", frame->pts, frame->format); else LOGV("audio frame pts=%lld, format=%d", frame->pts, frame->format);
else is = false;
if (cc==videoContext && rpRet==0)
{
frameCount++;
/*
struct SwsContext *sws_getContext( // 视频格式尺寸转换上下文,第一次调用时会有开销,后面就没了,多路视频格式尺寸转换时建议使用
int srcW,
int srcH,
enum AVPixelFormat srcFormat,
int dstW,
int dstH,
enum AVPixelFormat dstFormat,
int flags,
SwsFilter *srcFilter,
SwsFilter *dstFilter,
const double *param
);
struct SwsContext *sws_getCachedContext( // 与上面函数功能相同,就参数有一个差别,单个视频格式尺寸转换时建议使用
struct SwsContext *context, // 可以传NULL,返回为转换后的SwsContext,如果传非NULL时,如果后面要转换的参数与context的不匹配,那么context会被释放内存,然后重新创建一个符合参数的SwsContext返回
如果后面要转换的参数与context一致,那么直接返回context,此函数是线程不安全的
int srcW, // 原宽
int srcH, // 原高
enum AVPixelFormat srcFormat, // 原像素格式
int dstW, // 目标宽
int dstH, // 目标高
enum AVPixelFormat dstFormat, // 目标像素格式
int flags, // specify which algorithm and options to use for rescaling
SWS_FAST_BILINEAR 1
SWS_BILINEAR 2
SWS_BICUBIC 4
SWS_X 8
SWS_POINT 0x10
SWS_AREA 0x20
SWS_BICUBLIN 0x40
SwsFilter *srcFilter, // 过滤器,可以传NULL
SwsFilter *dstFilter, // 过滤器,可以传NULL
const double *param // 与参数 flags 算法相关,可以传NULL
);
*/
swsContext = sws_getCachedContext(swsContext, frame->width, frame->height, (AVPixelFormat) frame->format,
outWidth, outHeight, AV_PIX_FMT_RGBA,
SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (swsContext==NULL) LOGW("sws_getCachedContext failed.");
else
{
uint8_t * data[AV_NUM_DATA_POINTERS] = {0}; // AV_NUM_DATA_POINTERS 对应 AVFrame中data的长度
data[0] = rgba;
int lines[AV_NUM_DATA_POINTERS] = {0}; // AV_NUM_DATA_POINTERS 对应 AVFrame中linesize的长度
lines[0] = outWidth * 4;
/*
int sws_scale( // 每帧数据的处理(像素格式转换,尺寸转换),返回 the height of the output slice ,为0的话表示失败
struct SwsContext *c, // 像素格式尺寸转换上下文
const uint8_t *const srcSlice[], // 具体数据的数组,指针的数据,数据的长度由 enum AVPixelFormat srcFormat 参数决定,比如YUV、RGB交叉存放,没有平面存放的
const int srcStride[], // 对应 srcSlice 参数,对应 AVFrame 中的 linesize ,即一行数据的长度
int srcSliceY, // 深度 用不到,传0
int srcSliceH, // 原高度
uint8_t *const dst[], // 目标数据保存的地址
const int dstStride[] // 对应目标数据的linesize,即目标一行数据的长度
);
*/
int height = sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, frame->height, data, lines);
LOGV("视频像素格式转换 sws_scale height=%d", height); // sws_scale height=720 即outHeight的值
if (height>0) // 显示视频
{
int lockRet = ANativeWindow_lock(nwin, &wbuf, 0); // 锁住窗口,并将绘制窗口所需的数据的内存地址设置到wbuf.bits中, return 0 for success
if (lockRet!=0)
{
LOGW("surface窗口关闭了,解码结束.");
isGo = false;
}
uint8_t * dst = (uint8_t *) wbuf.bits; // 内存与显卡内存交换内存的地方
memcpy(dst, rgba, outWidth*outHeight*4); // 将rgba内存拷贝到dst
int unRet = ANativeWindow_unlockAndPost(nwin); // 解锁窗口,并post到主线程显示,return 0 for success
}
}
}
else if (cc==audioContext && rpRet==0)
{
uint8_t * out[2] = {0}; // 因为在上面设置重采样参数时设置了输出声道数固定为2,所以这里数组长度是2? 下标1的只是表示数组结尾NULL?
out[0] = pcm;
/*
int swr_convert( // 将一帧帧的音频转换为重采样,return number of samples output per channel, negative value on error
struct SwrContext *s, // 上下文
uint8_t **out, // 输出的数据
int out_count, // 输出的单通道的样本数量
const uint8_t **in, // 输入的数据
int in_count // 输入的单通道的样本数量,对应AVFrame中的nb_samples
);
*/
int len = swr_convert(swrContext, out, frame->nb_samples, (const uint8_t **) frame->data, frame->nb_samples);
LOGV("音频重采样 swr_convert len=%d, frame->nb_samples=%d", len, frame->nb_samples); // swr_convert len=1024, frame->nb_samples=1024
// 通过 AudioTrack 播放pcm音频
int out_buffer_size = av_samples_get_buffer_size(NULL, 2, frame->nb_samples, (AVSampleFormat) frame->format, 1); // 根据传入的参数计算每帧音频大小
audiotrack_write(env, out[0], out_buffer_size); // 在同一个线程中即使没做音视频同步处理,音视频也是同步的,但是只一个线程的话,音视频会卡顿
}
av_frame_unref(frame);
}
av_packet_unref(pkt);
}
ANativeWindow_release(nwin); // 释放窗口,java的surface引用计数会减1。 如果释放函数传的只是一级指针那么一般需要手动将指针置空,防止野指针
nwin = NULL;
delete [] rgba;
sws_freeContext(swsContext); // 释放内存,调用此函数后最好手动将 swsContext置空,防止野指针
swsContext = NULL;
delete [] pcm;
swr_free(&swrContext); // 释放内存
av_packet_free(&pkt);
av_frame_free(&frame);
avcodec_close(videoContext);
avcodec_free_context(&videoContext);
avcodec_close(audioContext);
avcodec_free_context(&audioContext);
avformat_close_input(&formatContext);
/*
AudioTrack 主要函数:
开始播放
public void play()throws IllegalStateException{}
停止播放音频数据,如果是STREAM模式,会等播放完最后写入buffer的数据才会停止。如果立即停止,要调用pause()方法,然后调用flush方法,会舍弃还没有播放的数据
public void stop()throws IllegalStateException{}
暂停播放,调用play()重新开始播放
public void pause()throws IllegalStateException {}
只在模式为STREAM下可用。将音频数据刷进等待播放的队列,任何写入的数据如果没有提交的话,都会被舍弃,但是并不能保证所有用于数据的缓冲空间都可用于后续的写入。
public void flush() {}
释放本地AudioTrack资源
public void release() {}
返回当前的播放状态
public int getPlayState() {}
*/
env->CallVoidMethod(obj_audiotrack, mid_release); // 释放 AudioTrack
env->DeleteGlobalRef(obj_audiotrack); // 删除全局引用
}
(五) FFMpeg音频重采样和视频格式转换和显示
最新推荐文章于 2024-05-29 11:23:38 发布