diff --git a/app/src/main/java/com/alexvas/rtsp/demo/decode/AudioDecodeThread.kt b/app/src/main/java/com/alexvas/rtsp/demo/decode/AudioDecodeThread.kt index f92b12e..34f3656 100644 --- a/app/src/main/java/com/alexvas/rtsp/demo/decode/AudioDecodeThread.kt +++ b/app/src/main/java/com/alexvas/rtsp/demo/decode/AudioDecodeThread.kt @@ -8,16 +8,17 @@ class AudioDecodeThread ( private val mimeType: String, private val sampleRate: Int, private val channelCount: Int, + private val codecConfig: ByteArray?, private val audioFrameQueue: FrameQueue) : Thread() { private val TAG: String = AudioDecodeThread::class.java.simpleName private val DEBUG = true companion object { - fun getAacDecoderConfigData(sampleRate: Int, channels: Int): ByteArray { + fun getAacDecoderConfigData(audioProfile: Int, sampleRate: Int, channels: Int): ByteArray { // AOT_LC = 2 // 0001 0000 0000 0000 - var extraDataAac = 2 shl 11 + var extraDataAac = audioProfile shl 11 // Sample rate when (sampleRate) { 7350 -> extraDataAac = extraDataAac or (0xC shl 7) @@ -50,7 +51,7 @@ class AudioDecodeThread ( val decoder = MediaCodec.createDecoderByType(mimeType) val format = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount) - val csd0 = getAacDecoderConfigData(sampleRate, channelCount) + val csd0 = codecConfig ?: getAacDecoderConfigData(MediaCodecInfo.CodecProfileLevel.AACObjectLC, sampleRate, channelCount) val bb = ByteBuffer.wrap(csd0) format.setByteBuffer("csd-0", bb) format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC) @@ -59,7 +60,9 @@ class AudioDecodeThread ( decoder.start() // Creating audio playback device - val bufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT) + val outChannel = if (channelCount > 1) AudioFormat.CHANNEL_OUT_STEREO else AudioFormat.CHANNEL_OUT_MONO + val outAudio = AudioFormat.ENCODING_PCM_16BIT + val bufferSize = AudioTrack.getMinBufferSize(sampleRate, outChannel, outAudio) // Log.i(TAG, "sampleRate: $sampleRate, bufferSize: $bufferSize".format(sampleRate, bufferSize)) val audioTrack = AudioTrack( AudioAttributes.Builder() @@ -67,8 +70,8 @@ class AudioDecodeThread ( .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .build(), AudioFormat.Builder() - .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) + .setEncoding(outAudio) + .setChannelMask(outChannel) .setSampleRate(sampleRate) .build(), bufferSize, @@ -100,9 +103,11 @@ class AudioDecodeThread ( e.printStackTrace() } } +// Log.i(TAG, "inIndex: ${inIndex}") try { val outIndex = decoder.dequeueOutputBuffer(bufferInfo, 10000) +// Log.w(TAG, "outIndex: ${outIndex}") when (outIndex) { MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> Log.d(TAG, "Decoder format changed: " + decoder.outputFormat) MediaCodec.INFO_TRY_AGAIN_LATER -> if (DEBUG) Log.d(TAG, "No output from decoder available") @@ -121,7 +126,7 @@ class AudioDecodeThread ( } } } - } catch (e: java.lang.Exception) { + } catch (e: Exception) { e.printStackTrace() } diff --git a/app/src/main/java/com/alexvas/rtsp/demo/ui/live/LiveFragment.kt b/app/src/main/java/com/alexvas/rtsp/demo/ui/live/LiveFragment.kt index f969cb9..78f0ac7 100644 --- a/app/src/main/java/com/alexvas/rtsp/demo/ui/live/LiveFragment.kt +++ b/app/src/main/java/com/alexvas/rtsp/demo/ui/live/LiveFragment.kt @@ -46,6 +46,7 @@ class LiveFragment : Fragment(), SurfaceHolder.Callback { private var audioMimeType: String = "" private var audioSampleRate: Int = 0 private var audioChannelCount: Int = 0 + private var audioCodecConfig: ByteArray? = null fun onRtspClientStarted() { if (DEBUG) Log.v(TAG, "onRtspClientStarted()") @@ -72,7 +73,7 @@ class LiveFragment : Fragment(), SurfaceHolder.Callback { } if (audioMimeType.isNotEmpty() && checkAudio!!.isChecked) { Log.i(TAG, "Starting audio decoder with mime type \"$audioMimeType\"") - audioDecodeThread = AudioDecodeThread(audioMimeType, audioSampleRate, audioChannelCount, audioFrameQueue) + audioDecodeThread = AudioDecodeThread(audioMimeType, audioSampleRate, audioChannelCount, audioCodecConfig, audioFrameQueue) audioDecodeThread?.start() } } @@ -134,6 +135,7 @@ class LiveFragment : Fragment(), SurfaceHolder.Callback { } audioSampleRate = sdpInfo.audioTrack?.sampleRateHz!! audioChannelCount = sdpInfo.audioTrack?.channels!! + audioCodecConfig = sdpInfo.audioTrack?.config } onRtspClientConnected() } @@ -163,12 +165,14 @@ class LiveFragment : Fragment(), SurfaceHolder.Callback { } Log.i(TAG, "NALs ($numNals): $textNals") } - videoFrameQueue.push(FrameQueue.Frame(data, offset, length, timestamp)) + if (length > 0) + videoFrameQueue.push(FrameQueue.Frame(data, offset, length, timestamp)) } override fun onRtspAudioSampleReceived(data: ByteArray, offset: Int, length: Int, timestamp: Long) { if (DEBUG) Log.v(TAG, "onRtspAudioSampleReceived(length=$length, timestamp=$timestamp)") - audioFrameQueue.push(FrameQueue.Frame(data, offset, length, timestamp)) + if (length > 0) + audioFrameQueue.push(FrameQueue.Frame(data, offset, length, timestamp)) } override fun onRtspConnecting() { diff --git a/library-rtsp/src/main/java/com/alexvas/rtsp/RtspClient.java b/library-rtsp/src/main/java/com/alexvas/rtsp/RtspClient.java index 9fee76a..299cc85 100644 --- a/library-rtsp/src/main/java/com/alexvas/rtsp/RtspClient.java +++ b/library-rtsp/src/main/java/com/alexvas/rtsp/RtspClient.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.math.BigInteger; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -192,6 +193,7 @@ public static class AudioTrack extends Track { public int sampleRateHz; // 16000, 8000 public int channels; // 1 - mono, 2 - stereo public String mode; // AAC-lbr, AAC-hbr + public @Nullable byte[] config; // config=1210fff15081ffdffc } private static final String CRLF = "\r\n"; @@ -859,6 +861,7 @@ private static Track[] getTracksFromDescribeParams(@NonNull List> params = getSdpAParams(param); if (params != null) { for (Pair pair: params) { - switch (pair.first) { + switch (pair.first.toLowerCase()) { case "sprop-parameter-sets": { String[] paramsSpsPps = TextUtils.split(pair.second, ","); @@ -1051,14 +1055,22 @@ private static void updateVideoTrackFromDescribeParam(@NonNull VideoTrack videoT } } + @NonNull + private static byte[] getBytesFromHexString(@NonNull String config) { + // "1210fff1" -> [12, 10, ff, f1] + return new BigInteger(config ,16).toByteArray(); + } + private static void updateAudioTrackFromDescribeParam(@NonNull AudioTrack audioTrack, @NonNull Pair param) { // a=fmtp:96 streamtype=5; profile-level-id=14; mode=AAC-lbr; config=1388; sizeLength=6; indexLength=2; indexDeltaLength=2; constantDuration=1024; maxDisplacement=5 // a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1408 + // a=fmtp:96 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1210fff15081ffdffc List> params = getSdpAParams(param); - if (params != null) { + if (params != null) { for (Pair pair: params) { - switch (pair.first) { + switch (pair.first.toLowerCase()) { case "mode": audioTrack.mode = pair.second; break; + case "config": audioTrack.config = getBytesFromHexString(pair.second); break; } } }