一、思路:
Java层:初始化一个surfaceView,赋值给Jni层作为播放载体,设置播放路径,在ffmpeg初始化完成后,开始正式播放本地视频。
Jni层:通过Java层赋值的surface获取到window对象(真正显示播放的对象窗体),在获取到视频路径后,初始化ffmpeg,初始化成功后通过反射通知Java层,开始解码视频,把读包线程得到的packet统一放到packet queue中,解码线程不停地从packet queue中获取packet,并进行解码,解码得到的frame对象统一放到frame queue中,显示线程不停地从frame queue中获取frame对象,并显示在window对象中。
二、上代码:
Java层:
private void initView() {
// Java层 第一步: 拿到一个surfaceView,作为播放载体
surfaceView = findViewById(R.id.surfaceView);
}
private void initPlayHelper() {
// Java层 第二步: 把surfaceView设置给playerHelper类
playerHelper = new PlayerHelper();
playerHelper.setSurfaceView(surfaceView);
// 设置相应的监听
playerHelper.setOnPrepareListener(this);
playerHelper.setOnProgressListener(this);
playerHelper.setOnErrorListener(this);
}
/**
* 开始播放画面
*
* @param view
*/
public void startPlay(View view) {
// Java层 第三步: 把播放路径设置给playerHelper类,(路径可以是本地路径,也可以是远端路径)
File inputMp4 = new File(Environment.getExternalStorageDirectory(), "ffmpeg-test.mp4"); //opensldemo.mp3
playerHelper.setDataSource(inputMp4.getAbsolutePath());
}
Jni层:
FfmpegHelper.cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_hele_zandroid_ffmpeg_PlayerHelper_native_1set_1surface(JNIEnv *env, jobject thiz,
jobject surface) {
// Jni层 第一步:通过Java层传递的surface,创建一个全局的window对象
// 创建window
if (window) {
ANativeWindow_release(window);
window = 0;
}
LOGD("native set window");
window = ANativeWindow_fromSurface(env, surface);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_hele_zandroid_ffmpeg_PlayerHelper_native_1prepare(JNIEnv *env, jobject instance,
jstring dataSource_) {
const char *dataSource = env->GetStringUTFChars(dataSource_, 0);
// Jni层 第二步:初始化ffmpeg
// 定义一个c层回调java层的辅助对象,使c层可以主动调用java层的方法
javaCallHelper = new JavaCallHelper(javaVM, env,
instance); // 这个instance就是PlayHelper.java对象
// 注意框架,交给控制层来处理
ffmpegHelper = new FfmpegHelper(javaCallHelper, dataSource);
// 设置渲染的回调,window对象在哪,就回调到哪
ffmpegHelper->setRenderFrame(renderFrame);
// 初始化ffmpeg
ffmpegHelper->prepare();
env->ReleaseStringUTFChars(dataSource_, dataSource);
}
/**
* 初始化ffmpeg
*/
void FfmpegHelper::initFfmpeg() {
// 4.0.5 ffmpeg版本,与3.4的版本的写法还有点不一样
// 初始化网络模块
avformat_network_init();
// 针对当前视频的总上下文 --- 第一个上下文
formatContext = avformat_alloc_context();
// 设置打开文件的超时时间
AVDictionary *avDictionary = NULL;
av_dict_set(&avDictionary, "timeout", "3000000", 0);
// 打开文件
int rst = avformat_open_input(&formatContext, dataSource, NULL, &avDictionary); //&avDictionary
// 如果打开失败,return
if (rst) {
// 回调给java层
if (javaCallHelper) {
javaCallHelper->onError(THREAD_CHILD, ERROR_OPEN_FILE_FAILED);
}
return;
}
// 查找流
rst = avformat_find_stream_info(formatContext, 0);
LOGD("avformat_find_stream_info rst = %d", rst);
if (rst < 0) {
if (javaCallHelper) {
javaCallHelper->onError(THREAD_CHILD, ERROR_FIND_STREAM_FAILED);
}
return;
}
videoChannel = NULL;
audioChannel = NULL;
for (int i = 0; i < formatContext->nb_streams; i++) {
AVCodecParameters *codecParameters = formatContext->streams[i]->codecpar;
// 找到解码器
AVCodec *decoder = avcodec_find_decoder(codecParameters->codec_id);
if (!decoder) {
if (javaCallHelper) {
javaCallHelper->onError(THREAD_CHILD, ERROR_FIND_DECODER_FAILED);
}
return;
}
// 创建解码器上下文
AVCodecContext *decoderContext = avcodec_alloc_context3(decoder);
if (!decoderContext) {
if (javaCallHelper) {
javaCallHelper->onError(THREAD_CHILD, ERROR_ALLOC_CODEC_CONTEXT_FAILED);
}
return;
}
// 把解码器信息赋值给上下文
avcodec_parameters_to_context(decoderContext, codecParameters);
// 重要,设置解码线程的个数,提升软解码速度
if (codecParameters->codec_type == AVMEDIA_TYPE_VIDEO) {
// 视频解码,多线程软解码提高解码效率
decoderContext->thread_count = 4; // 软解码线程,
}
// 打开解码器
if (avcodec_open2(decoderContext, decoder, NULL) != 0) {
if (javaCallHelper) {
javaCallHelper->onError(THREAD_CHILD, ERROR_OPEN_DECODER_FAILED);
}
return;
}
// 视频
if (codecParameters->codec_type == AVMEDIA_TYPE_VIDEO) {
// 这里就拿到了帧率
AVRational rational = formatContext->streams[i]->avg_frame_rate;
int fps = av_q2d(rational);
AVRational time_base = formatContext->streams[i]->time_base; // 时间显示的单位
// 视频通道的初始化 与 帧率设置(播放控制时用得到)
videoChannel = new VideoChannel(i, decoderContext, javaCallHelper, time_base);
videoChannel->setFps(fps);
// 把回调设置过去
if (renderFrame) {
videoChannel->setRenderFrame(renderFrame);
}
}
// 音频
else if (codecParameters->codec_type == AVMEDIA_TYPE_AUDIO) {
AVRational time_base = formatContext->streams[i]->time_base; // 时间显示的单位
audioChannel = new AudioChannel(i, decoderContext, javaCallHelper, time_base);
} else {
avcodec_parameters_free(&codecParameters);
}
}
// 把audioChannel对象 注入到 videoChannel中,是为了做音视频同步
if (videoChannel && audioChannel) {
videoChannel->setAudioChannel(audioChannel);
}
// 再判断一下音频/视频是否找到了
if (!videoChannel && !audioChannel) {
if (javaCallHelper) {
javaCallHelper->onError(THREAD_CHILD, ERROR_FIND_NO_MEDIA);
}
return;
}
// 回调java层,ffmpeg已经初始化好了,可以开始播放了
javaCallHelper->onPrepare(THREAD_CHILD);
}
/**
* 开始解码,把packet放到对应的packet queue中
*/
void FfmpegHelper::startDecode() {
int ret = 0;
AVPacket *packet = NULL;
while (isPlaying) {
// 防止解码的packet太多,导致内存溢出
......
// 相当于new 一个packet对象
packet = av_packet_alloc();
// 从媒体中读取到音频、视频包
ret = av_read_frame(formatContext, packet);
// 读取成功
if (ret == 0) {
// 把数据包放入队列
// 视频包
if (videoChannel && packet->stream_index == videoChannel->channelId) {
// 把pkt丢给videoChannel中的安全队列,剩下的由videoChannel来单独处理即可,责任清晰
videoChannel->pkt_queue.put(packet);
}
// 音频包
else if (audioChannel && packet->stream_index == audioChannel->channelId) {
audioChannel->pkt_queue.put(packet);
}
} else if (ret == AVERROR_EOF) {
// 读取完毕,但是不一定播放完毕
if (videoChannel->pkt_queue.empty() && videoChannel->frame_queue.empty()
&& audioChannel->pkt_queue.empty() && audioChannel->frame_queue.empty()) {
LOGD("播放完毕");
break;
}
// 因为seek的存在,就算读取完毕,依然要循环去执行av_read_frame,否则seek无效
} else {
break;
}
}
packet = NULL;
isPlaying = false;
pthread_exit(0);
}
VideoChannel.cpp
/**
* 解码packet并放到frame queue中
*/
void VideoChannel::decodePacket() {
LOGD("decodePacket start");
// 子线程
AVPacket *packet = 0;
AVFrame *frame;
int ret;
while (isPlaying) {
// 把队列中的数据读取到pkt_queue中
ret = pkt_queue.get(packet);
if (!isPlaying) {
break;
}
if (!ret) {
continue;
}
// 解码
avcodec_send_packet(decoderContext, packet);
frame = av_frame_alloc();
ret = avcodec_receive_frame(decoderContext, frame);
// packet资源释放
releasePacket(packet);
if (ret) {
// 没有读到帧数据
continue;
}
// 解码成功后,把frame对象放到frame队列中
frame_queue.put(frame);
// 如果frame队列大于100帧,则等一等
while (isPlaying && frame_queue.size() >= 100) {
av_usleep(10 * 1000);
continue;
}
}
// 释放packet
releasePacket(packet);
LOGD("decodePacket end");
}
/**
* 从frame queue中获取frame对象,并传给window对象真正显示
*/
void VideoChannel::showFrame() {
LOGD("show frame start");
// 转换器上下文
swsContext = sws_getContext(decoderContext->width, decoderContext->height,
decoderContext->pix_fmt, decoderContext->width,
decoderContext->height, AV_PIX_FMT_RGBA,
SWS_BILINEAR, 0, 0, 0);
AVFrame *frame = NULL;
int ret;
long long start, end;
double extra_delay, videoClock, timeDif;
uint8_t *dst_data[4];
// 每一行的首地址
int dst_linesize[4];
// 申请一张width * height, RGBA的图片内存大小 --- av_image_alloc不能放在while循环里,会内存溢出
av_image_alloc(dst_data, dst_linesize,
decoderContext->width, decoderContext->height,
AV_PIX_FMT_RGBA, 1); // 1代码左对齐
double delay = 0;
while (isPlaying) {
if (!isPlaying) {
break;
}
ret = frame_queue.get(frame);
if (!isPlaying) {
break;
}
if (!ret) {
continue;
}
// frame->data --- yuv数据
// frame->linesize --- yuv 每一行的首地址
// 0 --- y轴方向的偏移量
// frame->height --- 帧高度
// dst_data --- 转换后的rgba数据数组
// dst_linesize --- rgba数据数组的每一行的首地址
// yuv 转 rgba, 并把转换后的rgba数据,放在dst_data中
start = getCurrentTimeUs();
// 把frame中的数据,迁移到image对象中
sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height,
dst_data, dst_linesize);
// 把数据回调出去,在持有window处,真正来显示
renderFrame(dst_data[0], dst_linesize[0], decoderContext->width, decoderContext->height);
// 视频 与 音频 的时间戳 的差异, timeDif单位为 秒
videoClock = frame->pts * av_q2d(timeBase);
if (audioChannel) {
timeDif = videoClock - audioChannel->colock;
}
end = getCurrentTimeUs();
extra_delay =
frame->repeat_pict / (double) (2 * fps); // 如果没有重复帧,这个值为0;为重复帧时,导致的额外延时,单位 秒
// 统一转换为单位 秒
delay = (1.0f / fps) + extra_delay - (end - start) / 1000000.0f;
// LOGD("YUV 2 RGBA cost = %lld, delay show = %d", (end - start), delay);
delay = delay < 0 ? 0 : delay;
// 根据timeDif来动态调整休眠时间
adjustSleepTime(timeDif, delay);
// 释放 frame, RGBA数据都出来了,frame就没有用了
releaseFrame(frame);
}
releaseFrame(frame);
av_freep(&dst_data[0]);
isPlaying = false;
LOGD("show frame end");
}
/**
* 正在显示视频的处理方法,因为这个对象持有window对象
* @param data
* @param lineSize
* @param w
* @param h
*/
void renderFrame(uint8_t *data, int lineSize, int w, int h) {
// LOGD("native-lib renderFrame");
pthread_mutex_lock(&mutex);
if (!window) {
pthread_mutex_unlock(&mutex);
return;;
}
// 渲染
// nativeWindow缓冲区大小设置
ANativeWindow_setBuffersGeometry(window, w, h,
WINDOW_FORMAT_RGBA_8888);
ANativeWindow_Buffer outBuffer;
// 锁住window ---- 把window 和 outBuffer 绑定
if (ANativeWindow_lock(window, &outBuffer, 0)) {
// 如果锁定失败的处理
ANativeWindow_release(window);
window = 0;
pthread_mutex_unlock(&mutex);
return;
}
// 进行渲染
uint8_t *firstWindows = static_cast<uint8_t *>(outBuffer.bits);
// 输入源Rgba的
uint8_t *src_data = data;
// 拿到一行有多少个字节,需要 *4(stride 代表的是像素数量,一个像素4个字节)
int dstStride = outBuffer.stride * 4;
int srcStride = lineSize;
// 一行一行copy的原因,是因为window窗体大小,与image buffer的大小,有可能不一致。
// 是一个数据对齐的问题。
for (int y = 0; y < outBuffer.height; y++) {
// 内存拷贝,来进行渲染, 一行一行地copy, 长度以目的地为准
memcpy(firstWindows + y * dstStride, src_data + y * srcStride, dstStride);
}
// 解锁
ANativeWindow_unlockAndPost(window);
pthread_mutex_unlock(&mutex);
}
网友评论