• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

Android-player通过rtp接收h264数据并播放

android 搞代码 3年前 (2022-03-01) 22次浏览 已收录 0个评论

一、接收数据

rtp包的组包与拆包曾经由rtp 库实现,这里能够从rtp库的回调间接接管到原始发送的数据。

videoRtpWrapper.open(40018, 96, 90000);
        videoRtpWrapper.setCallback { data, len ->
            Log.d("dragon_video", "received video data $len")
            nalu.appended(data, len) { buffer, offset, size ->
                videoBufferQueue.put(buffer);
                videoBufferSizeQueue.put(size);
            }
        }

咱们能够看到rtp payload指定的类型是96,96代表的就是h264视频数据类型。这里监听的是偶数端口40018,奇数端口留给rtcp应用。咱们接管到的数据是nalu分片数据,咱们还须要把分片数据组成残缺的nalu数据。

二、分片组包

咱们应用NaluData这个工具类进行组包操作,相应的他也提供了拆包办法给发送端应用。

class NaluData {

    private val F_MASK = 0b10000000.toByte()
    private val NRI_MASK = 0b01100000.toByte()
    private val TYPE_MASK = 0b00011111.toByte()
    private val START_MASK = 0b10000000.toByte()
    private val END_MASK = 0b01000000.toByte()
    private val RESERVE_MASK = 0b00100000.toByte()
    private val maxFragmentSize = 65535 - 1000
    private val data = ByteArray(1000 * 100)
    private var position = 0;

    fun appended(buffer: ByteArray, len: Int, sender: (ByteArray, Int, Int) -> Unit) {
        val type = buffer[0] and TYPE_MASK
        //fu indicator type
        if (type == 0b11100.toByte()) {
            //fu indicator start/end flag
            val isStartFlag = (buffer[1] and START_MASK) == START_MASK
            val isEndFlag = (buffer[1] and END_MASK) == END_MASK
            when {
                isStartFlag -> {
                    data[0] = 0
                    data[1] = 0
                    data[2] = 0
                    data[3] = 1
                    position = 4
                    data[position] = (buffer[0] and (F_MASK or NRI_MASK)) or (buffer[1] and TYPE_MASK)
                    position++
                    System.arraycopy(buffer, 2, data, position, len - 2)
                    position += (len - 2)
                }
                isEndFlag -> {
                    System.arraycopy(buffer, 2, data, position, len - 2)
                    position += (len - 2)
                    sender.invoke(data, 0, position)
                }
                else -> {
                    System.arraycopy(buffer, 2, data, position, len - 2)
                    position += (len - 2)
                }
            }
        } else {
            data[0] = 0
            data[1] = 0
            data[2] = 0
            data[3] = 1
            position = 4
            System.arraycopy(buffer, 0, data, position, len)
            position += len
            sender.invoke(data, 0, position)
        }
    }
    ......
}

接管到数据后第一步是判断是否是分片数据,因为当数据没有超过udp限度的大小的时候,咱们能够间接发送原始的nalu数据,当数据超过限度的时候,咱们才进行分片操作。
首先咱们获取分片类型,依据是否能获取分片类型判断它是否是分片数据。先来看下分片数据的头部形成。

依据获取到的fu indicator type,fu indicator start/end flag进行组片操作。组片过程波及到将fu indicator和fu header转换成nalu header。依据插图中的形容,咱们能够不便的获取转变后的nalu header。组片后要记得将0001头增加到nalu header后面。

三、解码

分片组装成nalu数据后能够间接提供给MediaCode进行解码,解码后的数据通过output surface渲染。
为了可能正确的解码数据,咱们在初始化Mediacode阶段须要设置根本的视频信息。

        val videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 800)
        videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 750000)
        videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
        videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
        videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5)
        var currentTime = 0L
        videoDecodeCodec = object : SurfaceDecodeCodec(videoFormat, outputSurface) {
            override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
                val buffer = codec.getInputBuffer(index) ?: return;
                val data = videoBufferQueue.take()
                val size = videoBufferSizeQueue.take()
                val time = (System.currentTimeMillis() - currentTime) * 1000
                buffer.position(0)
                buffer.put(data, 0, size)
                codec.queueInputBuffer(index, 0, size, time, 0)
            }

            override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {

            }
        }

这里设置了视频的格局,分辨率,码率等信息。输出解码数据的时候须要指定数据的渲染工夫,我简略的应用以后工夫与解码器启动工夫的差值来计算的。

abstract class SurfaceDecodeCodec(mediaFormat: MediaFormat, val outputSurface: Surface) : BaseCodec("SurfaceDecodeCodec", mediaFormat) {
    override fun onCreateMediaCodec(mediaFormat: MediaFormat): MediaCodec {
        val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)
        val mediaCodecName = mediaCodecList.findDecoderForFormat(mediaFormat)
        return MediaCodec.createByCodecName(mediaCodecName)
    }

    override fun onConfigMediaCodec(mediaCodec: MediaCodec) {
        mediaCodec.configure(mediaFormat, outputSurface, null, 0)
    }

    override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
        codec.releaseOutputBuffer(index,true);
    }
}

因为咱们须要把解码的数据显示在ui上,所以这里配置了outputSurface来输入解码后的数据。这个outputSurface就是SurfaceView结构的surface。

结构outputSurface的代码就比较简单了。

class MainActivity : AppCompatActivity() {
    var rtpPlayer: PlayerRtp? = null;
    var outputSurface: Surface? = null;
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        surfaceView3.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {
                outputSurface = holder?.surface;
                rtpPlayer = PlayerRtp(outputSurface!!);
            }

            override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {

            }

            override fun surfaceDestroyed(holder: SurfaceHolder) {
                rtpPlayer?.release();
            }

        })
    }
}

在SurfaceView的callback中间接能够获取surface,咱们把这个surface提供给MediaCode渲染应用就能够了。

Git

https://github.com/mjlong123123/VideoPlayer


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Android-player通过rtp接收h264数据并播放

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址