年前那段时间太忙了,也一直没有时间整理。成功将携带FFmpeg的播放功能的版本上线了,这里就继续把FFmpeg的使用流程书写下去。我废话不多保证没坑,但是想要更近一步,一定要自己深入研究。
FFmpeg版本: ffmpeg-3.3.9
NDK 版本: android-ndk-r14c
系统:MAC OS
准备工作
第一节的时候已经告诉了如何编译so库,这一步就是把so库集成到自己的app中,其实就是Cmake的使用,不会的同学还是最好先学习一下Android的NDK开发
正式开始
注册
因为我使用的是FFmpeg3.0+所以需要进行注册,4.0之后就不需要了。
av_register_all();
avcodec_register_all();
avdevice_register_all();
解析内容
因为音视屏包含了视频流,音频流,字幕流。这些都要先解封装到AVFormatContext
AVFormatContext *avFormatContext = avformat_alloc_context();
avformat_network_init();
AVDictionary *opts = NULL;
int ret = avformat_open_input(&avFormatContext,inputPath,NULL,NULL);
if(ret ==0){
LOGI("打开成功");
} else{
LOGI("错误信息 %s",av_err2str(ret));
}
avformat_find_stream_info(avFormatContext,NULL);
暂时只是处理视频流
查找视屏流
//查找 视频流
int video_index = 1;//记录视频流的下标
for (int i = 0; i < avFormatContext->nb_streams; ++i) {
if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
video_index = i;
}
}
进行解码
//进行解码
AVCodec *avCodec = avcodec_find_decoder(avFormatContext->streams[video_index]->codecpar->codec_id);
AVCodecContext *avCodecContext = avFormatContext->streams[video_index]->codec;
if ((avcodec_open2(avCodecContext,avCodec,NULL))!=0){
LOGI("打开失败");
}
//申请 AvPacket 和AVFrame
// 其中AVPacket的作用是:保存解码之前的数据和一些附加信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等;
// AVFrame的作用是:存放解码过后的数据
AVPacket *avPacket = (AVPacket *)av_malloc(sizeof(AVPacket));
av_init_packet(avPacket);
AVFrame *avFrame = av_frame_alloc();//分配一个存储原始数据的AVFrame
AVFrame *rgb_frame = av_frame_alloc();//用来指向转成rgb之后的帧
//申请了缓存区
uint8_t *out_buffer= (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGBA,
avCodecContext->width,avCodecContext->height,1));
//与缓存区相关联,设置rgb_frame缓存区
int rgb_feame =av_image_fill_arrays(rgb_frame->data,rgb_frame->linesize,out_buffer,AV_PIX_FMT_RGBA,avCodecContext->width,avCodecContext->height,1);
if (rgb_feame !=0){
LOGI("错误信息 %s",av_err2str(rgb_feame));
}
SwsContext* swsContext = sws_getContext(avCodecContext->width,avCodecContext->height,avCodecContext->pix_fmt,
avCodecContext->width,avCodecContext->height,AV_PIX_FMT_RGBA,
SWS_BICUBIC,NULL,NULL,NULL);
哈哈,有没有看着很爽,其实就是这么简单。解码完了之后呢,当然是进行展示
//因为是原生绘制所以使用ANativeWindow
ANativeWindow *aNativeWindow = ANativeWindow_fromSurface(env,surface);
if(aNativeWindow == 0){
LOGI("nativewindow打开失败");
}
//一切准备妥当,开始解码
ANativeWindow_Buffer nativeWindowBuffer;
while (av_read_frame(avFormatContext,avPacket)>=0){
LOGI("解码");
if (avPacket->stream_index == video_index){
LOGI("开始解码了");
int re = avcodec_send_packet(avCodecContext, avPacket);
if (re != 0)
{
LOGI("解码失败第一步");
}
while( avcodec_receive_frame(avCodecContext, avFrame) == 0)
{
LOGI("转换并绘制");
ANativeWindow_setBuffersGeometry(aNativeWindow,avCodecContext->width,avCodecContext->height,WINDOW_FORMAT_RGBA_8888);
ANativeWindow_lock(aNativeWindow,&nativeWindowBuffer,NULL);
//转换为rgb格式
sws_scale(swsContext,(const uint8_t *const *)avFrame->data,avFrame->linesize,0,
avFrame->height,rgb_frame->data,
rgb_frame->linesize);
// rgb_frame是有画面数据
uint8_t *dst= (uint8_t *) nativeWindowBuffer.bits;
// 拿到一行有多少个字节 RGBA
int destStride=nativeWindowBuffer.stride*4;
//像素数据的首地址
uint8_t * src= rgb_frame->data[0];
// 实际内存一行数量
int srcStride = rgb_frame->linesize[0];
//int i=0;
for (int i = 0; i < avCodecContext->height; ++i) {
// memcpy(void *dest, const void *src, size_t n)
//将rgb_frame中每一行的数据复制给nativewindow
memcpy(dst + i * destStride, src + i * srcStride, srcStride);
}
LOGI("绘制成功");
//解锁
ANativeWindow_unlockAndPost(aNativeWindow);
usleep(1000 * 16);
}
}
av_packet_unref(avPacket);
}
咳咳,最后了,最后了。记住些Java的小伙伴一定要记得手动释放掉内存,这里可没有垃圾回收机制
// /释放
ANativeWindow_release(aNativeWindow);
av_frame_free(&avFrame);
av_frame_free(&rgb_frame);
avcodec_close(avCodecContext);
avformat_free_context(avFormatContext);
一切搞定,有问题喊我。我是贴心大白。比心。还有这里其实跟实际使用还是有差距的,比如图像被拉伸了怎么办?留个疑问,用来督促你自己去研究。等我再有空的时候,我会写个完整的播放器交给你。
网友评论