Skip to content

Commit e387ec5

Browse files
committed
Tried making it more optimized
1 parent 1146f6d commit e387ec5

3 files changed

Lines changed: 184 additions & 277 deletions

File tree

Lines changed: 140 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -1,257 +1,197 @@
11
package com.ipcamera
22

33
import android.annotation.SuppressLint
4-
import android.graphics.ImageFormat
4+
import android.graphics.SurfaceTexture
55
import android.hardware.camera2.*
6-
import android.media.ImageReader
7-
import android.os.Bundle
8-
import android.os.Handler
9-
import android.os.Looper
6+
import android.media.MediaCodec
7+
import android.media.MediaCodecInfo
8+
import android.media.MediaFormat
9+
import android.os.*
1010
import android.util.Log
1111
import android.util.Range
12-
import android.view.SurfaceHolder
12+
import android.view.Surface
13+
import android.view.TextureView
1314
import android.widget.Toast
1415
import androidx.appcompat.app.AppCompatActivity
1516
import com.ipcamera.databinding.StreamActivityBinding
1617
import java.io.DataOutputStream
18+
import java.net.InetSocketAddress
1719
import java.net.Socket
18-
import java.util.concurrent.ConcurrentLinkedQueue
20+
import java.nio.ByteBuffer
1921
import java.util.concurrent.Executors
2022

21-
2223
class StreamActivity : AppCompatActivity() {
23-
2424
private lateinit var binding: StreamActivityBinding
25-
private val TAG = "StreamTag"
26-
private lateinit var imageReader: ImageReader
25+
private val TAG = "HEVCStream"
2726

28-
@Volatile
29-
private var isStreaming = false
27+
private lateinit var mediaCodec: MediaCodec
28+
private lateinit var cameraDevice: CameraDevice
3029

31-
@Volatile
30+
@Volatile private var isStreaming = false
3231
private var socket: Socket? = null
32+
private var outputStream: DataOutputStream? = null
33+
34+
private val codecExecutor = Executors.newSingleThreadExecutor()
35+
private val networkExecutor = Executors.newSingleThreadExecutor()
3336

34-
private val executor = Executors.newSingleThreadExecutor()
37+
private val WIDTH = 1920
38+
private val HEIGHT = 1080
39+
private val FPS = 60
40+
private val BITRATE = 10_000_000
3541

3642
@SuppressLint("MissingPermission")
3743
override fun onCreate(savedInstanceState: Bundle?) {
3844
super.onCreate(savedInstanceState)
39-
4045
binding = StreamActivityBinding.inflate(layoutInflater)
41-
4246
setContentView(binding.root)
4347

4448
val cameraManager = getSystemService(CameraManager::class.java)
45-
4649
val cameraId = cameraManager.cameraIdList[0]
50+
val ipAddress = SettingsPreferences(this).getIpAddress()!!
4751

48-
val surfaceView = binding.surfaceView
49-
50-
val mainHandler = Handler(Looper.getMainLooper())
51-
52-
imageReader = ImageReader.newInstance(1280, 720, ImageFormat.JPEG, 3)
53-
54-
val queue = ConcurrentLinkedQueue<ByteArray>()
55-
56-
surfaceView.holder.setFixedSize(1920, 1080)
52+
binding.surfaceView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
53+
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
54+
setupEncoder()
55+
startCamera(cameraManager, cameraId)
56+
}
5757

58-
val ipAddress = SettingsPreferences(this.applicationContext).getIpAddress()!!
58+
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
59+
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean = false
60+
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
61+
}
5962

6063
binding.btnSave.setOnClickListener {
61-
if (isStreaming) {
62-
isStreaming = !isStreaming
63-
64-
executor.execute {
65-
socket?.close()
66-
socket = null
67-
68-
mainHandler.post {
69-
binding.tvStatus.text = "Status: Disconnected"
70-
binding.btnSave.text = "Start streaming"
71-
}
72-
}
73-
74-
75-
} else {
76-
binding.tvStatus.text = "Connecting..."
77-
78-
executor.execute {
79-
try {
80-
val ip = ipAddress.split(":")[0]
81-
val port = ipAddress.split(":")[1]
82-
83-
socket = Socket(ip, port.toInt())
84-
socket?.sendBufferSize = 900000000
85-
socket?.receiveBufferSize = 900000000
86-
87-
mainHandler.post {
88-
binding.tvStatus.text = "Streaming to: $ipAddress"
89-
binding.btnSave.text = "Stop streaming"
90-
}
91-
92-
isStreaming = !isStreaming
93-
94-
val socketWriter = DataOutputStream(socket!!.getOutputStream())
95-
val stack = ArrayDeque<Int>(10)
96-
var size = 0
97-
var start = 0L
98-
99-
while (isStreaming) {
100-
val frame = try {
101-
queue.remove()
102-
} catch (ex: java.util.NoSuchElementException) {
103-
Log.d(TAG, "Empty queue")
104-
continue
105-
}
106-
107-
Log.d(TAG, "Buffer size: ${queue.size}")
108-
start = System.currentTimeMillis()
109-
size = frame.size
110-
111-
while (size > 0) {
112-
stack.addLast(size % 10)
113-
size /= 10
114-
}
115-
116-
socketWriter.writeByte(stack.size)
117-
118-
while (stack.isNotEmpty()) {
119-
socketWriter.writeByte(stack.removeLast())
120-
}
121-
122-
socketWriter.write(frame)
123-
124-
socketWriter.flush()
125-
Log.d(TAG, "Sent to server: ${frame.size} bytes")
126-
Log.d(TAG, "Elapsed to send: ${System.currentTimeMillis() - start}")
127-
}
128-
129-
} catch (exception: java.lang.Exception) {
130-
exception.printStackTrace()
131-
132-
socket?.close()
133-
socket = null
134-
isStreaming = false
135-
136-
mainHandler.post {
137-
Toast.makeText(
138-
this,
139-
"Could not connect to: $ipAddress",
140-
Toast.LENGTH_LONG
141-
)
142-
.show()
143-
binding.tvStatus.text = "Status: Disconnected"
144-
binding.btnSave.text = "Start streaming"
145-
}
146-
}
147-
}
148-
}
64+
if (isStreaming) stopStreaming()
65+
else startStreaming(ipAddress)
14966
}
67+
}
15068

151-
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
152-
override fun surfaceCreated(holder: SurfaceHolder) {
153-
Log.d(TAG, "surfaceCreated: ")
154-
155-
imageReader.setOnImageAvailableListener(object :
156-
ImageReader.OnImageAvailableListener {
157-
override fun onImageAvailable(reader: ImageReader?) {
69+
private fun setupEncoder() {
70+
val format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT)
71+
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
72+
format.setInteger(MediaFormat.KEY_BIT_RATE, BITRATE)
73+
format.setInteger(MediaFormat.KEY_FRAME_RATE, FPS)
74+
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) // 1 sec between I-frames
15875

159-
val image = reader?.acquireNextImage() ?: return
76+
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC)
77+
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
78+
}
16079

161-
if (!isStreaming) {
162-
image.close()
163-
return
80+
private fun startCamera(manager: CameraManager, cameraId: String) {
81+
val mainHandler = Handler(Looper.getMainLooper())
82+
manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
83+
override fun onOpened(camera: CameraDevice) {
84+
cameraDevice = camera
85+
val surface = mediaCodec.createInputSurface()
86+
val previewSurface = Surface(binding.surfaceView.surfaceTexture)
87+
88+
camera.createCaptureSession(listOf(surface, previewSurface),
89+
object : CameraCaptureSession.StateCallback() {
90+
override fun onConfigured(session: CameraCaptureSession) {
91+
val requestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
92+
requestBuilder.addTarget(surface)
93+
requestBuilder.addTarget(previewSurface)
94+
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range(FPS, FPS))
95+
96+
session.setRepeatingRequest(requestBuilder.build(), null, mainHandler)
97+
mediaCodec.start()
16498
}
16599

166-
val buffer = image.planes[0].buffer
167-
buffer.rewind()
168-
169-
val arr = ByteArray(buffer.capacity())
170-
171-
var i = 0
172-
while (buffer.hasRemaining()) {
173-
arr[i++] = buffer.get()
100+
override fun onConfigureFailed(session: CameraCaptureSession) {
101+
Log.e(TAG, "Camera session config failed")
174102
}
103+
}, mainHandler
104+
)
105+
}
175106

176-
image.close()
177-
queue.add(arr)
178-
}
179-
}, null)
180-
181-
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
182-
override fun onOpened(camera: CameraDevice) {
183-
Log.d(TAG, "onOpened")
184-
185-
val captureRequest =
186-
camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
187-
188-
captureRequest.set(CaptureRequest.JPEG_QUALITY, 20)
189-
val range = Range(24, 24)
190-
191-
captureRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, range)
107+
override fun onDisconnected(camera: CameraDevice) {}
108+
override fun onError(camera: CameraDevice, error: Int) {
109+
Log.e(TAG, "Camera error: $error")
110+
}
111+
}, mainHandler)
112+
}
192113

193-
val callback = object : CameraCaptureSession.CaptureCallback() {
114+
private fun startStreaming(ipAddress: String) {
115+
binding.tvStatus.text = "Connecting..."
194116

195-
override fun onCaptureProgressed(
196-
session: CameraCaptureSession,
197-
request: CaptureRequest,
198-
partialResult: CaptureResult
199-
) {
200-
super.onCaptureProgressed(session, request, partialResult)
201-
Log.d(TAG, "onCaptureProgressed: ")
202-
}
203-
}
117+
networkExecutor.execute {
118+
try {
119+
val ip = ipAddress.split(":")[0]
120+
val port = ipAddress.split(":")[1].toInt()
204121

205-
val captureSession = object : CameraCaptureSession.StateCallback() {
206-
override fun onConfigured(session: CameraCaptureSession) {
207-
captureRequest.addTarget(imageReader.surface)
208-
captureRequest.addTarget(surfaceView.holder.surface)
209-
session.setRepeatingRequest(
210-
captureRequest.build(),
211-
callback,
212-
mainHandler
213-
)
214-
}
122+
socket = Socket()
123+
socket?.tcpNoDelay = true
124+
socket?.connect(InetSocketAddress(ip, port), 3000)
125+
outputStream = DataOutputStream(socket!!.getOutputStream())
126+
isStreaming = true
215127

216-
override fun onConfigureFailed(session: CameraCaptureSession) {
128+
runOnUiThread {
129+
binding.tvStatus.text = "Streaming to: $ipAddress"
130+
binding.btnSave.text = "Stop streaming"
131+
}
217132

133+
codecExecutor.execute {
134+
val bufferInfo = MediaCodec.BufferInfo()
135+
while (isStreaming) {
136+
val index = mediaCodec.dequeueOutputBuffer(bufferInfo, 10_000)
137+
if (index >= 0) {
138+
val encodedData = mediaCodec.getOutputBuffer(index) ?: continue
139+
if (bufferInfo.size > 0) {
140+
val packet = ByteArray(bufferInfo.size)
141+
encodedData.position(bufferInfo.offset)
142+
encodedData.limit(bufferInfo.offset + bufferInfo.size)
143+
encodedData.get(packet)
144+
145+
// send length + frame
146+
outputStream?.writeInt(packet.size)
147+
outputStream?.write(packet)
148+
outputStream?.flush()
218149
}
150+
mediaCodec.releaseOutputBuffer(index, false)
219151
}
220-
221-
222-
camera.createCaptureSession(
223-
listOf(
224-
surfaceView.holder.surface,
225-
imageReader.surface
226-
), captureSession, mainHandler
227-
)
228-
229-
}
230-
231-
override fun onDisconnected(camera: CameraDevice) {
232-
233-
}
234-
235-
override fun onError(camera: CameraDevice, error: Int) {
236-
237152
}
153+
}
238154

239-
}, mainHandler)
155+
} catch (e: Exception) {
156+
e.printStackTrace()
157+
runOnUiThread {
158+
Toast.makeText(this, "Could not connect to $ipAddress", Toast.LENGTH_LONG).show()
159+
binding.tvStatus.text = "Disconnected"
160+
binding.btnSave.text = "Start streaming"
161+
}
162+
stopStreaming()
240163
}
164+
}
165+
}
241166

242-
override fun surfaceChanged(
243-
holder: SurfaceHolder,
244-
format: Int,
245-
width: Int,
246-
height: Int
247-
) {
167+
private fun stopStreaming() {
168+
isStreaming = false
169+
socket?.close()
170+
outputStream = null
171+
socket = null
248172

249-
}
173+
if (this::mediaCodec.isInitialized) {
174+
mediaCodec.stop()
175+
mediaCodec.release()
176+
}
250177

251-
override fun surfaceDestroyed(holder: SurfaceHolder) {
178+
runOnUiThread {
179+
binding.tvStatus.text = "Disconnected"
180+
binding.btnSave.text = "Start streaming"
181+
}
182+
}
252183

253-
}
184+
override fun onDestroy() {
185+
super.onDestroy()
186+
if (this::cameraDevice.isInitialized) {
187+
cameraDevice.close()
188+
}
189+
if (this::mediaCodec.isInitialized) {
190+
mediaCodec.release()
191+
}
192+
}
254193

255-
})
194+
private fun DataOutputStream.writeInt(value: Int) {
195+
write(ByteBuffer.allocate(4).putInt(value).array())
256196
}
257197
}

0 commit comments

Comments
 (0)