美文网首页
第二节:使用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