• EasyPlayer Android安卓流媒体播放器实现播放同步录像功能实现(附源码)


    本文转自EasyDarwin团队John的博客:http://blog.csdn.net/jyt0551,John是EasyPusher安卓直播推流、EasyPlayer直播流媒体播放端的开发和维护者,在这方面为开源社区贡献了非常多的技术干货和代码,这里对John的辛苦劳作表示感谢!

    在之前一片博客《 EasyPusher实现安卓Android手机直播推送同步录像功能》(http://blog.csdn.net/jyt0551/article/details/58714595)中,我写到了EasyPusher推送的同时进行本地存储的功能,我们今天来介绍下EasyPlayer保存本地录像的功能。EasyPlayer同样是运用MediaMuxer进行录像的,与EasyPusher不同的是,Player要保存的是远端的音视频码流。目前Player支持对H264格式的视频和AAC格式的音频进行存储。

    这里写图片描述

    在前一篇博客 ,音视频码流的metadata,即MediaFormat,是从MediaCodec取出来的。也就是说硬编码库提供了获取音视频的metadata的接口。但是很可惜我们在播放端并没有这样方便的借口可以调用。那MediaFormat对象只能我们手动构建了。

    MediaFormat这个类的实现非常简单,它的内部以键值对的形式对音视频的参数进行了封装,并且向外提供了接口以供读写。因而我们可以创建一个MediaFormat对象,并使用特定的参数对其赋值即可。经作者研究发现,在录像时,对于视频流,需要的metadata如下表所示。

    数据 说明
    KEY_MIME 视频的MIME,比如video/avc
    width 宽度
    height 高度
    csd-0 SPS
    csd-1 PPS

    对于音频,需要如下信息:

    数据 说明
    KEY_MIME 音频的MIME,比如audio/mp4a-latm
    KEY_CHANNEL_COUNT 通道数
    KEY_SAMPLE_RATE 采样率
    csd-0 一些更多的细节信息,比如profile、sample的索引等。参考exoplayer里的音频数据的处理
    csd-1 这个。。更多的细节,就不太清楚了。作者也是参考了exoplayer里面的处理

    了解了这些基本信息后,接下来我们要做的就是从码流中获取到这些信息,并构建MediaFormat,用来添加Video或Audio Track.
    下面是获取到视频相关信息后,添加一个VideoTrack的代码。

    // 添加Video Track
    MediaFormat format = new MediaFormat();
    format.setInteger(MediaFormat.KEY_WIDTH, mWidth);
    format.setInteger(MediaFormat.KEY_HEIGHT, mHeight);
    mCSD0.clear();
    format.setByteBuffer("csd-0", mCSD0);
    mCSD1.clear();
    format.setByteBuffer("csd-1", mCSD1);
    format.setString(MediaFormat.KEY_MIME, "video/avc");
    
    
    Log.i(TAG, String.format("addTrack video track :%s", format));
    mMuxerVideoTrack = muxer.addTrack(format);

    下面是添加AudioTrack的代码。

    int audioObjectType = 2;
    byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAacAudioSpecificConfig(audioObjectType, sampleRateIndex, channelConfig);
    Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(audioSpecificConfig);
    //                                format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
    format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
    format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, audioParams.second);
    format.setInteger(MediaFormat.KEY_SAMPLE_RATE, audioParams.first);
    
    List<byte[]> bytes = Collections.singletonList(audioSpecificConfig);
    for (int j = 0; j < bytes.size(); j++) {
        format.setByteBuffer("csd-" + j, ByteBuffer.wrap(bytes.get(j)));
    }
    
    Log.i(TAG, String.format("addTrack audio track :%s", format));
    mMuxerAudioTrack = muxer.addTrack(format);

    至此,音视频的通道都已经添加完成,接下来就是要写数据了。代码如下:

    private synchronized void pumpSample(RTSPClient.FrameInfo frameInfo) {
          if (mObject == null) return;
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
              MediaMuxer muxer = (MediaMuxer) mObject;
              MediaCodec.BufferInfo bi = new MediaCodec.BufferInfo();
              bi.offset = frameInfo.offset;
              bi.size = frameInfo.length;
              ByteBuffer buffer = ByteBuffer.wrap(frameInfo.buffer, bi.offset, bi.size);
              bi.presentationTimeUs = frameInfo.stamp;
              try {
                  if (frameInfo.audio) {
                      bi.offset += 7;
                      bi.size -= 7;
                      if (mMuxerAudioTrack > -1)
                          muxer.writeSampleData(mMuxerAudioTrack, buffer, bi);
                  } else if (mMuxerVideoTrack > -1) {
                      if (frameInfo.type != 1) {
                          bi.flags = 0;
                      } else {
                          bi.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME;
                      }
                      muxer.writeSampleData(mMuxerVideoTrack, buffer, bi);
                  }
              } catch (IllegalStateException ex) {
                  ex.printStackTrace();
              } catch (IllegalArgumentException ex) {
                  ex.printStackTrace();
              }
          }
      }

    最后,在录像完成后,关闭MediaMuxer,释放相关资源。

    
        public synchronized void stopRecord() {
            mMuxerAudioTrack = mMuxerVideoTrack = -1;
            mRecordingPath = null;
            if (mObject == null) return;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                MediaMuxer muxer = (MediaMuxer) mObject;
                try {
                    muxer.release();
                } catch (IllegalStateException ex) {
                    ex.printStackTrace();
                } catch (IllegalArgumentException ex) {
                    ex.printStackTrace();
                }
            }
            mObject = null;
    
            ResultReceiver rr = mRR;
            if (rr != null) {
                rr.send(RESULT_RECORD_END, null);
            }
        }

    获取更多信息

    邮件:support@easydarwin.org

    WEB:www.EasyDarwin.org

    Copyright © EasyDarwin.org 2012-2017

    EasyDarwin

  • 相关阅读:
    (一)Kubernetes 系统基础
    Linux-网络RAID技术DRBD
    Linux-存储服务之NFS
    ELK快速入门(五)配置nginx代理kibana
    ELK快速入门(四)filebeat替代logstash收集日志
    ELK快速入门(三)logstash收集日志写入redis
    渗透测试工具集合
    CVE-2019-0708(非蓝屏poc)远程桌面代码执行漏洞复现
    代码审计-thinkphp3.2.3框架漏洞sql注入
    渗透测试-端口复用正向后门
  • 原文地址:https://www.cnblogs.com/babosa/p/9217851.html
Copyright © 2020-2023  润新知