因此,我们有点想出了如何使用Camera2 API拍照和录制视频。 剩下的只是学习如何将视频流从Android设备传输到外部受苦的接收者。 正如之前反复提到的,最终目标是机器人的智能化-我们在上面安装了智能手机,可以说,将猴子变成了人。 媒体编解码器将为此提供帮助。 当然还有新的Camera2 API。
谁在乎,请在猫下。
您可以在
此处找到有关机器人项目的详细信息,但就
目前而言 ,我们将直接将视频(或与其连接的Android智能手机)流式传输到个人电子计算机。
为此我们需要什么?
如您所知,为了将视频流从智能手机屏幕传输到其他地方,首先需要将其(流)转换为合适的压缩格式(它太厚,无法逐帧传输),设置时间戳(时间戳)并以二进制形式发送给接收者。 它将执行逆解码操作。
从2013年Android 4.3发行之日起,Media Codec类就一直在处理这些低级的黑手党。

另一件事是,与今天不同的是,较早采用视频编码并不是那么简单。 为了从相机上获取图片,有必要使用大量的
神秘密码 ,其中唯一的错误可能导致应用程序彻底崩溃,就像Yakut萨满巫师的咒语一样。 再加上以前的Camera API,您必须自己编写不同的同步笔,而不是现成的回调,并且此活动不适合胆小的人。
最重要的是,您从远处看工作
代码,从总体上看,一切似乎都很清楚。 您开始将零件转移到您的项目中-目前尚不清楚为什么要浇筑它。 但是,由于难以理解细节,因此无法纠正。
是的,并且从固体中以某种方式轻松
过时 。 简而言之,一团糟
幸运的是,对于那些机智的人,Google建设者已经引入了
Surface的神奇概念,通过它可以避免出现底层细节。 作为开发人员,我付出了什么代价,却输了什么钱,这对我来说是很难理解的,但是现在我们几乎可以直言不讳地说:“ Android,将这个Surface摄像头的视频显示在此Surface上,而无需更改其中的任何代码并继续发送。” 最令人惊奇的是,它有效。 有了新的Camera2 API,程序本身就知道何时发送数据,出现了新的回调!
所以现在要编码视频-随地吐痰。 我们现在要做什么。
我们从
第一篇文章中获取代码,并且像往常一样,将所有内容扔掉,除了按钮和相机初始化外。
让我们从应用程序的布局开始。<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextureView android:id="@+id/textureView" android:layout_width="356dp" android:layout_height="410dp" android:layout_marginTop="32dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.49" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:layout_width="292dp" android:layout_height="145dp" android:layout_marginStart="16dp" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textureView" app:layout_constraintVertical_bias="0.537"> <Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button3" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
并以Media Codec挂钩结束
在上一篇文章中,我们在Surface上显示了来自摄像头的图像,并使用MediaRecorder编写了视频。 为此,我们只需在“曲面”列表中指定两个组件。
(Arrays.asList(surface, mMediaRecorder.getSurface()).
这是同一件事,我们只指定mMediaRecorder而不是:
(Arrays.asList(surface, mEncoderSurface),
原来是这样的:
private void startCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); texture.setDefaultBufferSize(320, 240); surface = new Surface(texture); try { mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mPreviewBuilder.addTarget(surface); mPreviewBuilder.addTarget(mEncoderSurface); mCameraDevice.createCaptureSession(Arrays.asList(surface, mEncoderSurface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mSession = session; try { mSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { } }, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } }
什么是mEncoderSurface? 这与Media Codec将使用的Surface相同。 仅对于初学者,您需要大约以这种方式来初始化它们。
private void setUpMediaCodec() { try { mCodec = MediaCodec.createEncoderByType("video/avc");
现在,只需注册一个回调。 当媒体编解码器突然感觉到可以进一步广播的下一个数据准备就绪时,他将通过以下方式通知我们:
private class EncoderCallback extends MediaCodec.Callback { @Override public void onInputBufferAvailable(MediaCodec codec, int index) { } @Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { outPutByteBuffer = mCodec.getOutputBuffer(index); byte[] outDate = new byte[info.size]; outPutByteBuffer.get(outDate); Log.i(LOG_TAG, " outDate.length : " + outDate.length); mCodec.releaseOutputBuffer(index, false); } @Override public void onError(MediaCodec codec, MediaCodec.CodecException e) { Log.i(LOG_TAG, "Error: " + e); } @Override public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { Log.i(LOG_TAG, "encoder output format changed: " + format); } }
outDate字节数组是真正的宝藏。 它包含现成的H264编码视频流片段,我们现在可以用它执行我们想做的任何事情。
他们在这里...

有些片段可能太大,无法通过网络传输,但是如果有必要,系统不会自行将它们切碎并发送给接收者。
但是,如果它非常可怕,那么您可以通过推碎这样的碎片来粉碎自己
int count =0; int temp =outDate.length ; do {
但就目前而言,我们需要亲眼看到缓冲区中的数据确实是H264视频流。 因此,让我们将它们发送到文件中:
我们将在设置中编写:
private void setUpMediaCodec() { File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test3.h264"); try { outputStream = new BufferedOutputStream(new FileOutputStream(mFile)); Log.i("Encoder", "outputStream initialized"); } catch (Exception e) { e.printStackTrace(); }
在回调中,缓冲区在哪里:
try { outputStream.write(outDate, 0, outDate.length);
打开应用程序,按按钮:“打开相机和流”。 录制自动开始。 我们稍等一下,然后按停止按钮。
由于格式不是MP4,因此保存的文件通常不会丢失,但是如果您使用VLC播放器打开该文件或
使用ONLINE CONVERT在线转换它,我们将确保我们走在正确的轨道上。 的确,图像位于侧面,但是可以修复。
通常,对于录制,拍照或流媒体的每个事件,最好每次都打开一个新会话并关闭旧会话。 也就是说,首先我们打开相机并启动裸露的预览。 然后,如果需要拍照,请关闭预览并打开预览,但要固定图像阅读器。 如果我们切换到视频录制,请关闭当前会话并在连接了预览和Media Recorder的情况下开始会话。 我没有这样做,因此代码的可见性不会受到影响,您可以决定如何为自己提供更多便利。
这是整个代码。
基本媒体编解码器 package com.example.basicmediacodec; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.StrictMode; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import android.widget.Toast; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; public static Surface surface = null; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private Button mButtonOpenCamera1 = null; private Button mButtonStreamVideo = null; private Button mButtonTStopStreamVideo = null; public static TextureView mImageView = null; private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private MediaCodec mCodec = null;
并且不要忘记清单中的权限。
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET"/>
因此,我们确保媒体编解码器正在运行。 但是,用它将视频写入文件是不可行的。 Media Recorder可以更好地处理此任务,并且可以增加声音。 因此,我们将再次丢弃文件部分,并添加一个代码块,以使用udp协议将视频流式传输到网络。 这也很简单。
首先,我们实际上是初始化UDP服务器。
DatagramSocket udpSocket; String ip_address = "192.168.1.84";
在同一个回调中,我们将准备数据发送到文件流中,现在,我们将以数据报的形式将它们发送到我们的家庭网络(我希望每个人都拥有吗?)
try { DatagramPacket packet = new DatagramPacket(outDate, outDate.length, address, port); udpSocket.send(packet); } catch (IOException e) { Log.i(LOG_TAG, " UDP "); }
这就是全部吗?
看来,但没有。 该应用程序将在启动时启动。 您会看到,该系统与主流不同,我们发送各种数据报包。 但是没有理由恐慌。 首先,尽管我们在主线程中,但是我们仍然异步工作,即触发回调。 其次,发送udp数据包是相同的异步过程。 我们仅告诉操作系统发送数据包会很好,但是在此问题上我们完全依靠它。 因此,为了使Android不会反叛,我们将在程序的开头添加两行:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy);
总的来说,下面的小型
优雅演示程序将会出现:
package com.example.basicmediacodec; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.StrictMode; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import android.widget.Toast; import java.io.BufferedOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; public static Surface surface = null; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private Button mButtonOpenCamera1 = null; private Button mButtonStreamVideo = null; private Button mButtonTStopStreamVideo = null; public static TextureView mImageView = null; private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private MediaCodec mCodec = null;
我不知道其他人会怎么做,但是在我的Red Note 7上,您甚至可以看到如何在正确的地址下载千字节

而且有很多这样的udp套接字,多少网络带宽就足够了。 最主要的是在哪里有地址。 您将进行广播。
现在让我们在计算机上查找所需的地址
我必须说,并不是每个计算机程序都能够通过单个udp通道吸收和消化H264视频流,而没有任何其他信息。 但是有些可能。 例如,这是非常著名的
VLC媒体
播放器 。 这是一件很酷的事情,如果您开始描述它的功能,那么从本文中您会得到一整本书。 当然可以。 如果没有,戴上它。
从命令描述来看,udp数据包可以消化该播放器。
URL syntax: file:
从理论上讲,不需要所有这些源地址和绑定地址。 仅需要侦听端口。

, , ( )

, ?
( )
, , VLC . .
?
. , , VLC 3.08 Vetinari? , udp deprecated .
- . udp -:
, , RTP . — , udp ( ), . , - , . . , , .
. -, , — . udp .
- RTP - . , , . VLC , udp .
,
VLC 2.2.6 Umbrella( VLC), .
.
, H264. - VLC , ( , ). - , , VLC . , ?
.

«» . , - , .
:
C:\Program Files\VideoLAN\VLC\vlc udp:
.
. JAVA . .