版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huanghuangjin/article/details/81914103
#include "common.hpp"
#include <android/native_window.h>
#include <android/native_window_jni.h>
JNIEXPORT void JNICALL Java_hankin_hjmedia_mpeg_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;
}
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 = getAVDecoder6_1(videoContext, formatContext->streams[videoStream]->codecpar, true, false);
if (gvRet!=0) return;
AVCodecContext * audioContext = avcodec_alloc_context3(NULL);
int gaRet = getAVDecoder6_1(audioContext, formatContext->streams[audioStream]->codecpar, false);
if (gaRet!=0) return;
AVPacket * pkt = av_packet_alloc();
AVFrame * frame = av_frame_alloc();
long long start = getNowMs();
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 (getNowMs()-start >= 3000)
{
LOGW("3秒内平均每秒解码视频帧数 : %d", frameCount/3);
start = getNowMs();
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);
if (rpRet==0) if (cc==videoContext) LOGV("video frame->pts=%lld", frame->pts); else LOGV("audio frame->pts=%lld", frame->pts);
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
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
}
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);
avcodec_free_context(&videoContext);
avcodec_free_context(&audioContext);
avformat_close_input(&formatContext);
}