美文网首页
第二节:使用FFmpeg3.0+进行视频播放

第二节:使用FFmpeg3.0+进行视频播放

作者: WY_a111 | 来源:发表于2020-03-26 21:06 被阅读0次

年前那段时间太忙了,也一直没有时间整理。成功将携带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);

一切搞定,有问题喊我。我是贴心大白。比心。还有这里其实跟实际使用还是有差距的,比如图像被拉伸了怎么办?留个疑问,用来督促你自己去研究。等我再有空的时候,我会写个完整的播放器交给你。

相关文章

网友评论

      本文标题:第二节:使用FFmpeg3.0+进行视频播放

      本文链接:https://www.haomeiwen.com/subject/hqrhuhtx.html