Android开发经验谈Android技术知识Android开发

手把手教学音视频开发,手写低延时H265码流投屏

2023-07-25  本文已影响0人  谁动了我的代码

H.265(又称为HEVC,High Efficiency Video Coding)是一种高效的视频编码标准,是H.264(或称为AVC,Advanced Video Coding)的继任者。H.265旨在提供更高的压缩效率和更好的视频质量,以满足现代视频传输和存储需求。

手写低延时H265码流投屏

想要手写出低延时H265码流投屏,需要先学习投屏架构 、 MediaProjection与MeidiaCodec交互机制、H265码流交换、I帧之前配置VPS SPS PPS等。

投屏架构

H.265编码投屏架构通常包括以下几个主要组件:

  1. 编码器:使用H.265编码算法将音视频信号压缩为更小的数据量,以便在网络上传输或存储。
  2. 播放器/解码器:接收编码后的数据,并使用H.265解码算法将其解压缩为原始的音视频信号。
  3. 投屏设备:接收解码后的音视频信号,并将其显示在屏幕上或通过扬声器播放出来。
  4. 网络传输:音视频数据可以通过有线或无线网络传输到投屏设备。常用的网络传输协议包括HTTP,RTSP,RTMP等。
  5. 控制协议:用于控制投屏设备的操作,例如开始播放,暂停,调整音量等。常用的控制协议包括DLNA,AirPlay,Miracast等。

整个H.265编码投屏架构的流程可以简单描述为:原始音视频信号经过编码器进行压缩,然后通过网络传输到接收端,接收端使用解码器解压缩音视频数据,并将其显示或播放出来。同时,可以通过控制协议来控制投屏设备的操作。

MediaProjection与MeidiaCodec交互机制

MediaProjection和MediaCodec是Android中两个关键的多媒体处理类,它们可以结合使用以实现屏幕录制和编码功能。

MediaProjection类提供了一种方式,允许应用程序捕获和录制设备屏幕上的内容。它允许应用程序获取一个MediaProjection对象,然后使用该对象创建一个屏幕录制器。录制器可以通过MediaProjection的createVirtualDisplay()方法创建一个虚拟显示器,该显示器将屏幕内容捕获并输出到指定的Surface上。

MediaCodec类是用于音视频编码和解码的重要类。它可以将原始的音视频数据进行编码或解码,以便在网络上传输或存储。MediaCodec使用底层硬件或软件编解码器来完成编码和解码过程。

在实现屏幕录制和编码的过程中,MediaProjection和MediaCodec可以通过以下方式进行交互:

  1. MediaProjection创建一个虚拟显示器,并将其Surface传递给MediaCodec。这样,MediaCodec就可以将屏幕内容渲染到指定的Surface上。
  2. MediaCodec使用Surface作为输入,将屏幕内容编码为H.264或H.265等格式的视频数据。
  3. 编码后的视频数据可以通过MediaCodec的输出缓冲区获取。应用程序可以获取这些缓冲区,并将其传递给网络传输或存储。
  4. 应用程序可以通过MediaCodec的配置参数,如设置编码器的码率、帧率等来控制编码的质量和性能。

需要注意的是,MediaProjection和MediaCodec是两个独立的类,它们之间没有直接的交互机制。

H265码流交换

H.265码流交换是指在不同设备之间传输和共享H.265编码的视频数据。

H.265码流交换通常涉及以下几个主要环节:

  1. 编码器生成H.265码流:使用H.265编码算法,将原始的音视频信号压缩为H.265码流。
  2. 码流传输:H.265码流可以通过网络传输或存储介质传输到其他设备。常见的传输方式包括网络传输协议(如RTSP、RTMP、HLS等)和存储介质(如硬盘、SD卡等)。
  3. 解码器解码:接收到H.265码流的设备使用H.265解码算法将其解码为原始的音视频信号。
  4. 显示或播放:解码后的音视频信号可以在设备的屏幕上显示或通过扬声器播放出来。

在H.265码流交换过程中,需要确保发送方和接收方都支持H.265编解码。如果接收方不支持H.265解码,可以通过转码将H.265码流转换为其他格式(如H.264)再进行传输。

I帧之前配置VPS SPS PPS

在H.265编码中,I帧(关键帧)之前需要配置VPS(Video Parameter Set)、SPS(Sequence Parameter Set)和PPS(Picture Parameter Set)。

在H.265编码中,通常的编码顺序是先配置VPS,然后配置SPS,接着在每个I帧之前配置PPS。这样,在解码端收到视频数据时,可以根据VPS、SPS和PPS的配置信息来正确解码视频帧。

这种配置VPS、SPS和PPS的方式有助于提高视频流的压缩效率和传输效率。由于VPS、SPS和PPS只需配置一次,后续的视频帧只需传输编码后的数据,无需重复传输这些参数信息。同时,解码端可以根据配置信息来正确解码视频帧,提高解码的准确性和效率。

手写H265码流投屏代码简单示例

下面是一个简单示例,展示如何使用低延时的H.265编码器将屏幕内容进行编码,并通过网络实时传输到另一台设备进行投屏显示。

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;

import java.io.IOException;
import java.nio.ByteBuffer;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
    private static final int REQUEST_CODE = 1;
    private static final String MIME_TYPE = "video/hevc";
    private static final int FRAME_RATE = 30;
    private static final int BIT_RATE = 1000000;
    private static final int IFRAME_INTERVAL = 1;

    private MediaProjectionManager mProjectionManager;
    private MediaProjection mMediaProjection;
    private MediaCodec mEncoder;
    private Surface mInputSurface;
    private SurfaceView mSurfaceView;
    private Button mStartButton;
    private boolean mIsEncoding = false;

    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSurfaceView = findViewById(R.id.surface_view);
        mStartButton = findViewById(R.id.start_button);
        mStartButton.setOnClickListener(new View.OnClickListener() {
            @Override            public void onClick(View v) {
                if (mIsEncoding) {
                    stopScreenRecording();
                } else {
                    startScreenRecording();
                }
            }
        });

        mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        mSurfaceView.getHolder().addCallback(this);
    }

    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
            mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
            startEncoder();
        }
    }

    @Override    public void surfaceCreated(SurfaceHolder holder) {
        // Prepare the encoder when the surface is created        prepareEncoder();
    }

    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override    public void surfaceDestroyed(SurfaceHolder holder) {
        // Stop the encoder when the surface is destroyed        stopEncoder();
    }

    private void startScreenRecording() {
        if (mMediaProjection == null) {
            startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
        } else {
            startEncoder();
        }
    }

    private void stopScreenRecording() {
        stopEncoder();
        mMediaProjection.stop();
    }

    private void prepareEncoder() {
        try {
            DisplayMetrics metrics = new DisplayMetrics();
            Display display = getWindowManager().getDefaultDisplay();
            display.getRealMetrics(metrics);
            int screenWidth = metrics.widthPixels;
            int screenHeight = metrics.heightPixels;

            MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, screenWidth, screenHeight);
            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
            format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);

            mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
            mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
           

全文主要讲解在音视频开发当中的,如何手写H265码流投屏的步骤学习,想要进阶更多音视频技术可以参考《音视频全套进阶资料》点击可以查看详细类目。

步骤总结

低延时H.265码流投屏的实现主要包括以下几个步骤:

  1. 获取屏幕内容:使用MediaProjectionManager获取MediaProjection对象,并通过createScreenCaptureIntent()方法启动屏幕捕捉,获取屏幕内容。
  2. 配置编码器:创建MediaCodec对象,并配置编码器的参数,包括编码类型(MIME_TYPE)、帧率(FRAME_RATE)、比特率(BIT_RATE)等。
  3. 创建输入Surface:通过MediaCodec的createInputSurface()方法创建输入Surface,用于将屏幕内容传输给编码器。
  4. 启动编码器:调用MediaCodec的configure()方法配置编码器,然后调用MediaCodec的start()方法启动编码器。
  5. 获取屏幕内容并编码:将屏幕内容绘制到输入Surface上,编码器会将输入Surface中的内容进行H.265编码,并生成码流数据。
  6. 传输码流数据:将编码器生成的码流数据通过网络传输到接收端。可以使用传输协议(如RTSP、RTMP、HLS等)将码流数据实时传输到接收端。
  7. 解码和显示:接收端使用H.265解码器将接收到的码流数据解码为原始的音视频信号,并将解码后的内容显示在屏幕上。
上一篇下一篇

猜你喜欢

热点阅读