diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/FlvMuxer.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/FlvMuxer.kt index 3e340bd1a..a49b6a4f4 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/FlvMuxer.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/FlvMuxer.kt @@ -23,9 +23,9 @@ import io.github.thibaultbee.streampack.internal.data.PacketType import io.github.thibaultbee.streampack.internal.interfaces.IOrientationProvider import io.github.thibaultbee.streampack.internal.muxers.IMuxer import io.github.thibaultbee.streampack.internal.muxers.IMuxerListener -import io.github.thibaultbee.streampack.internal.muxers.flv.packet.FlvHeader -import io.github.thibaultbee.streampack.internal.muxers.flv.packet.FlvTagFactory -import io.github.thibaultbee.streampack.internal.muxers.flv.packet.OnMetadata +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.AVTagsFactory +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.FlvHeader +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.OnMetadata import io.github.thibaultbee.streampack.internal.utils.TimeUtils import io.github.thibaultbee.streampack.internal.utils.extensions.isAudio import io.github.thibaultbee.streampack.internal.utils.extensions.isVideo @@ -73,7 +73,7 @@ class FlvMuxer( } frame.pts -= startUpTime!! - val flvTags = FlvTagFactory(frame, true, streams[streamPid]).build() + val flvTags = AVTagsFactory(frame, streams[streamPid]).build() flvTags.forEach { listener?.onOutputFrame( Packet( diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/FlvMuxerHelper.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/FlvMuxerHelper.kt index 6c2bf1b2e..2cd92e0d0 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/FlvMuxerHelper.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/FlvMuxerHelper.kt @@ -19,10 +19,10 @@ import android.media.MediaFormat import io.github.thibaultbee.streampack.internal.muxers.IAudioMuxerHelper import io.github.thibaultbee.streampack.internal.muxers.IMuxerHelper import io.github.thibaultbee.streampack.internal.muxers.IVideoMuxerHelper -import io.github.thibaultbee.streampack.internal.muxers.flv.packet.CodecID -import io.github.thibaultbee.streampack.internal.muxers.flv.packet.SoundFormat -import io.github.thibaultbee.streampack.internal.muxers.flv.packet.SoundRate -import io.github.thibaultbee.streampack.internal.muxers.flv.packet.SoundSize +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.video.CodecID +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.SoundFormat +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.SoundRate +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.SoundSize class FlvMuxerHelper : IMuxerHelper { override val video = VideoFlvMuxerHelper() @@ -35,7 +35,10 @@ class VideoFlvMuxerHelper : IVideoMuxerHelper { */ override val supportedEncoders: List get() { - return CodecID.values().mapNotNull { + val extendedSupportedCodec = listOf( + MediaFormat.MIMETYPE_VIDEO_HEVC + ) + val supportedCodecList = CodecID.values().mapNotNull { try { it.toMimeType() } catch (e: Exception) { @@ -43,10 +46,10 @@ class VideoFlvMuxerHelper : IVideoMuxerHelper { } }.filter { listOf( - MediaFormat.MIMETYPE_VIDEO_AVC, - MediaFormat.MIMETYPE_VIDEO_HEVC + MediaFormat.MIMETYPE_VIDEO_AVC ).contains(it) } + return supportedCodecList + extendedSupportedCodec } } diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/FlvTagsFactory.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/FlvTagsFactory.kt deleted file mode 100644 index fa2c9f1a7..000000000 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/FlvTagsFactory.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2022 Thibault B. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.github.thibaultbee.streampack.internal.muxers.flv.packet - -import io.github.thibaultbee.streampack.data.Config -import io.github.thibaultbee.streampack.internal.data.Frame -import io.github.thibaultbee.streampack.internal.utils.extensions.isAudio - -class FlvTagFactory( - private val frame: Frame, - private val alsoWriteSequenceHeader: Boolean = true, - private val config: Config -) { - fun build(): List { - val flvTags = mutableListOf() - if (alsoWriteSequenceHeader && (frame.isKeyFrame || frame.isAudio)) { - // Create a reference FlvTag - flvTags.add(FlvTag.createFlvTag(frame, isSequenceHeader = true, config)) - } - - flvTags.add(FlvTag.createFlvTag(frame, isSequenceHeader = false, config)) - return flvTags - } -} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/VideoTag.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/VideoTag.kt deleted file mode 100644 index 8df9d2837..000000000 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/VideoTag.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2022 Thibault B. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.github.thibaultbee.streampack.internal.muxers.flv.packet - -import android.media.MediaFormat -import io.github.thibaultbee.streampack.data.VideoConfig -import io.github.thibaultbee.streampack.internal.utils.av.video.avc.AVCDecoderConfigurationRecord -import io.github.thibaultbee.streampack.internal.utils.av.video.hevc.HEVCDecoderConfigurationRecord -import io.github.thibaultbee.streampack.internal.utils.extensions.put -import io.github.thibaultbee.streampack.internal.utils.extensions.putInt24 -import io.github.thibaultbee.streampack.internal.utils.extensions.removeStartCode -import io.github.thibaultbee.streampack.internal.utils.extensions.startCodeSize -import java.io.IOException -import java.nio.ByteBuffer - -class VideoTag( - pts: Long, - private val buffers: List, - private val isKeyFrame: Boolean, - private val isSequenceHeader: Boolean = false, - private val videoConfig: VideoConfig -) : - FlvTag(pts, TagType.VIDEO) { - constructor( - pts: Long, - buffer: ByteBuffer, - isKeyFrame: Boolean, - isSequenceHeader: Boolean = false, - videoConfig: VideoConfig - ) : this(pts, listOf(buffer), isKeyFrame, isSequenceHeader, videoConfig) - - companion object { - private const val VIDEO_TAG_HEADER_SIZE = 1 - } - - init { - if (videoConfig.mimeType == MediaFormat.MIMETYPE_VIDEO_AVC) { - if (!isSequenceHeader) { - require(buffers.size == 1) { "Only one buffer is expected for raw frame" } - } else { - require(buffers.size == 2) { "Both SPS and PPS are expected in sequence mode" } - } - } - if (videoConfig.mimeType == MediaFormat.MIMETYPE_VIDEO_HEVC) { - if (!isSequenceHeader) { - require(buffers.size == 1) { "Only one buffer is expected for raw frame" } - } else { - require(buffers.size == 3) { "Both SPS, PPS and VPS are expected in sequence mode" } - } - } - } - - override fun writeTagHeader(buffer: ByteBuffer) { - val frameType = if (isKeyFrame) { - FrameType.KEY - } else { - FrameType.INTER - } - buffer.put( - (frameType.value shl 4) or // Frame Type - (CodecID.fromMimeType(videoConfig.mimeType).value) // CodecId - ) - if ((videoConfig.mimeType == MediaFormat.MIMETYPE_VIDEO_AVC) - || (videoConfig.mimeType == MediaFormat.MIMETYPE_VIDEO_HEVC) - ) { - if (isSequenceHeader) { - buffer.put(AVCPacketType.SEQUENCE.value) // AVC sequence header - } else { - buffer.put(AVCPacketType.NALU.value) // AVC NALU - } - buffer.putInt24(0) // TODO: CompositionTime - } - } - - override val tagHeaderSize = computeHeaderSize() - - private fun computeHeaderSize(): Int { - var size = VIDEO_TAG_HEADER_SIZE - if ((videoConfig.mimeType == MediaFormat.MIMETYPE_VIDEO_AVC) - || (videoConfig.mimeType == MediaFormat.MIMETYPE_VIDEO_HEVC) - ) { - size += 4 // AVCPacketType & CompositionTime - } - return size - } - - override fun writePayload(buffer: ByteBuffer) { - if (isSequenceHeader) { - when (videoConfig.mimeType) { - MediaFormat.MIMETYPE_VIDEO_AVC -> { - AVCDecoderConfigurationRecord.fromParameterSets(buffers[0], buffers[1]) - .write(buffer) - } - - MediaFormat.MIMETYPE_VIDEO_HEVC -> { - HEVCDecoderConfigurationRecord.fromParameterSets(buffers).write(buffer) - } - - else -> { - throw IOException("Mimetype ${videoConfig.mimeType} is not supported") - } - } - - } else { - val noStartCodeBuffer = buffers[0].removeStartCode() - buffer.putInt(noStartCodeBuffer.remaining()) - buffer.put(noStartCodeBuffer) - } - } - - override val payloadSize = computePayloadSize() - - private fun computePayloadSize(): Int { - return if (isSequenceHeader) { - when (videoConfig.mimeType) { - MediaFormat.MIMETYPE_VIDEO_AVC -> { - AVCDecoderConfigurationRecord.getSize(buffers[0], buffers[1]) - } - - MediaFormat.MIMETYPE_VIDEO_HEVC -> { - HEVCDecoderConfigurationRecord.getSize(buffers[0], buffers[1], buffers[2]) - } - - else -> { - throw IOException("Mimetype ${videoConfig.mimeType} is not supported") - } - } - } else { - return buffers[0].remaining() - buffers[0].startCodeSize + 4 // Replace start code with annex B - } - } -} - -enum class FrameType(val value: Int) { - KEY(1), - INTER(2), - DISPOSABLE_INTER(3), - GENERATED_KEY(4), - INFO_COMMAND(5) -} - -enum class CodecID(val value: Int) { - SORENSON_H263(2), - SCREEN_1(3), - VP6(4), - VP6_ALPHA(5), - SCREEN_2(6), - AVC(7), - HEVC(12); // Not standards - - fun toMimeType() = when (this) { - SORENSON_H263 -> MediaFormat.MIMETYPE_VIDEO_H263 - AVC -> MediaFormat.MIMETYPE_VIDEO_AVC - HEVC -> MediaFormat.MIMETYPE_VIDEO_HEVC - else -> throw IOException("MimeType is not supported: $this") - } - - companion object { - fun fromMimeType(mimeType: String) = when (mimeType) { - MediaFormat.MIMETYPE_VIDEO_H263 -> SORENSON_H263 - MediaFormat.MIMETYPE_VIDEO_AVC -> AVC - MediaFormat.MIMETYPE_VIDEO_HEVC -> HEVC - else -> throw IOException("MimeType is not supported: $mimeType") - } - } -} - -enum class AVCPacketType(val value: Int) { - SEQUENCE(0), - NALU(1), - END_OF_SEQUENCE(2) -} \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/AVTagsFactory.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/AVTagsFactory.kt new file mode 100644 index 000000000..53d468b39 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/AVTagsFactory.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 Thibault B. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.thibaultbee.streampack.internal.muxers.flv.tags + +import android.media.MediaFormat +import io.github.thibaultbee.streampack.data.AudioConfig +import io.github.thibaultbee.streampack.data.Config +import io.github.thibaultbee.streampack.data.VideoConfig +import io.github.thibaultbee.streampack.internal.data.Frame +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.video.PacketType +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.video.VideoTagFactory +import java.io.IOException + +class AVTagsFactory( + private val frame: Frame, + private val config: Config +) { + fun build(): List { + return if (frame.isVideo) { + createVideoTags(frame, config as VideoConfig) + } else if (frame.isAudio) { + createAudioTags(frame, config as AudioConfig) + } else { + throw IOException("Frame is neither video nor audio: ${frame.mimeType}") + } + } + + private fun createAudioTags( + frame: Frame, + config: AudioConfig + ): List { + return listOf( + AudioTag( + frame.pts, + frame.extra!![0], + if (config.mimeType == MediaFormat.MIMETYPE_AUDIO_AAC) { + AACPacketType.SEQUENCE_HEADER + } else { + null + }, + config + ), + AudioTag( + frame.pts, + frame.buffer, + if (config.mimeType == MediaFormat.MIMETYPE_AUDIO_AAC) { + AACPacketType.RAW + } else { + null + }, + config + ) + ) + } + + private fun createVideoTags( + frame: Frame, + config: VideoConfig + ): List { + val videoTags = mutableListOf() + + if (frame.isKeyFrame) { + videoTags.add( + VideoTagFactory( + frame.pts, + frame.extra!!, + true, + PacketType.SEQUENCE_START, + config.mimeType + ).build() + ) + } + + videoTags.add( + VideoTagFactory( + frame.pts, + frame.buffer, + frame.isKeyFrame, + PacketType.CODED_FRAMES_X, // For extended codec onlu. + config.mimeType + ).build() + ) + + return videoTags + } +} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/AudioTag.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/AudioTag.kt similarity index 88% rename from core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/AudioTag.kt rename to core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/AudioTag.kt index 96623e8d9..869c9b7d4 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/AudioTag.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/AudioTag.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.github.thibaultbee.streampack.internal.muxers.flv.packet +package io.github.thibaultbee.streampack.internal.muxers.flv.tags import android.media.AudioFormat import android.media.MediaFormat @@ -26,7 +26,7 @@ import java.nio.ByteBuffer class AudioTag( pts: Long, private val frameBuffer: ByteBuffer, - private val isSequenceHeader: Boolean = false, + private val aacPacketType: AACPacketType?, private val audioConfig: AudioConfig ) : FlvTag(pts, TagType.AUDIO) { @@ -42,11 +42,7 @@ class AudioTag( (SoundType.fromChannelConfig(audioConfig.channelConfig).value) ) if (audioConfig.mimeType == MediaFormat.MIMETYPE_AUDIO_AAC) { - if (isSequenceHeader) { - buffer.put(0) - } else { - buffer.put(1) - } + buffer.put(aacPacketType!!.value) } } @@ -60,15 +56,19 @@ class AudioTag( return size } - override fun writePayload(buffer: ByteBuffer) { - if (isSequenceHeader) { - AudioSpecificConfig.writeFromByteBuffer(buffer, frameBuffer, audioConfig) + override fun writeBody(buffer: ByteBuffer) { + if (audioConfig.mimeType == MediaFormat.MIMETYPE_AUDIO_AAC) { + if (aacPacketType == AACPacketType.SEQUENCE_HEADER) { + AudioSpecificConfig.writeFromByteBuffer(buffer, frameBuffer, audioConfig) + } else { + buffer.put(frameBuffer) + } } else { buffer.put(frameBuffer) } } - override val payloadSize = frameBuffer.remaining() + override val bodySize = frameBuffer.remaining() } enum class SoundFormat(val value: Int) { @@ -163,4 +163,9 @@ enum class SoundType(val value: Int) { else -> throw IOException("Channel config is not supported: $channelConfig") } } +} + +enum class AACPacketType(val value: Int) { + SEQUENCE_HEADER(0), + RAW(1) } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/FlvHeader.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/FlvHeader.kt similarity index 95% rename from core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/FlvHeader.kt rename to core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/FlvHeader.kt index 47888f27b..f5c94fb0f 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/FlvHeader.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/FlvHeader.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.github.thibaultbee.streampack.internal.muxers.flv.packet +package io.github.thibaultbee.streampack.internal.muxers.flv.tags import io.github.thibaultbee.streampack.internal.utils.extensions.shl import io.github.thibaultbee.streampack.internal.utils.extensions.toByte diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/FlvTag.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/FlvTag.kt similarity index 53% rename from core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/FlvTag.kt rename to core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/FlvTag.kt index b07916b73..f7c8ed0f9 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/FlvTag.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/FlvTag.kt @@ -13,18 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.github.thibaultbee.streampack.internal.muxers.flv.packet +package io.github.thibaultbee.streampack.internal.muxers.flv.tags -import io.github.thibaultbee.streampack.data.AudioConfig -import io.github.thibaultbee.streampack.data.Config -import io.github.thibaultbee.streampack.data.VideoConfig -import io.github.thibaultbee.streampack.internal.data.Frame import io.github.thibaultbee.streampack.internal.utils.extensions.put import io.github.thibaultbee.streampack.internal.utils.extensions.putInt24 import io.github.thibaultbee.streampack.internal.utils.extensions.shl -import io.github.thibaultbee.streampack.internal.utils.extensions.isAudio -import io.github.thibaultbee.streampack.internal.utils.extensions.isVideo -import java.io.IOException import java.nio.ByteBuffer abstract class FlvTag( @@ -34,11 +27,11 @@ abstract class FlvTag( ) { protected abstract fun writeTagHeader(buffer: ByteBuffer) protected abstract val tagHeaderSize: Int - protected abstract fun writePayload(buffer: ByteBuffer) - protected abstract val payloadSize: Int + protected abstract fun writeBody(buffer: ByteBuffer) + protected abstract val bodySize: Int fun write(): ByteBuffer { - val dataSize = tagHeaderSize + payloadSize + val dataSize = tagHeaderSize + bodySize val flvTagSize = FLV_HEADER_TAG_SIZE + dataSize val buffer = ByteBuffer.allocateDirect(flvTagSize + 4) // 4 - PreviousTagSize @@ -59,7 +52,7 @@ abstract class FlvTag( // FilterParams } - writePayload(buffer) + writeBody(buffer) buffer.putInt(flvTagSize) @@ -70,36 +63,6 @@ abstract class FlvTag( companion object { private const val FLV_HEADER_TAG_SIZE = 11 - - fun createFlvTag(frame: Frame, isSequenceHeader: Boolean, config: Config): FlvTag { - return when { - frame.isAudio -> if (isSequenceHeader) { - AudioTag(frame.pts, frame.extra!![0], true, config as AudioConfig) - } else { - AudioTag(frame.pts, frame.buffer, false, config as AudioConfig) - } - frame.isVideo -> if (isSequenceHeader) { - VideoTag( - frame.pts, - frame.extra!!, - frame.isKeyFrame, - true, - config as VideoConfig - ) - } else { - VideoTag( - frame.pts, - frame.buffer, - frame.isKeyFrame, - false, - config as VideoConfig - ) - } - else -> { - throw IOException("Frame is neither video nor audio: ${frame.mimeType}") - } - } - } } } diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/OnMetadata.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/OnMetadata.kt similarity index 71% rename from core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/OnMetadata.kt rename to core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/OnMetadata.kt index 762e57c09..c7073c707 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/OnMetadata.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/OnMetadata.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.github.thibaultbee.streampack.internal.muxers.flv.packet +package io.github.thibaultbee.streampack.internal.muxers.flv.tags import io.github.thibaultbee.streampack.data.AudioConfig import io.github.thibaultbee.streampack.data.Config @@ -21,7 +21,12 @@ import io.github.thibaultbee.streampack.data.VideoConfig import io.github.thibaultbee.streampack.internal.interfaces.IOrientationProvider import io.github.thibaultbee.streampack.internal.muxers.flv.amf.containers.AmfContainer import io.github.thibaultbee.streampack.internal.muxers.flv.amf.containers.AmfEcmaArray +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.video.CodecID +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.video.ExtendedVideoTag +import io.github.thibaultbee.streampack.internal.utils.av.FourCCs import io.github.thibaultbee.streampack.internal.utils.extensions.numOfBits +import io.github.thibaultbee.streampack.logger.Logger +import io.github.thibaultbee.streampack.utils.TAG import java.io.IOException import java.nio.ByteBuffer @@ -55,17 +60,31 @@ class OnMetadata( ) } + is VideoConfig -> { val resolution = orientationProvider.orientedSize(it.resolution) - ecmaArray.add( - "videocodecid", - CodecID.fromMimeType(it.mimeType).value.toDouble() - ) + val videoCodecID = if (ExtendedVideoTag.isSupportedCodec(it.mimeType)) { + FourCCs.fromMimeType(it.mimeType).value.code + } else { + try { + CodecID.fromMimeType(it.mimeType).value + } catch (e: Exception) { + Logger.e(TAG, "Failed to get videocodecid for: ${it.mimeType}") + null + } + } + videoCodecID?.let { codecId -> + ecmaArray.add( + "videocodecid", + codecId.toDouble() + ) + } ecmaArray.add("videodatarate", it.startBitrate.toDouble() / 1000) // to Kpbs ecmaArray.add("width", resolution.width.toDouble()) ecmaArray.add("height", resolution.height.toDouble()) ecmaArray.add("framerate", it.fps.toDouble()) } + else -> { throw IOException("Not supported mime type: ${it.mimeType}") } @@ -81,10 +100,10 @@ class OnMetadata( override val tagHeaderSize: Int get() = 0 - override fun writePayload(buffer: ByteBuffer) { + override fun writeBody(buffer: ByteBuffer) { amfContainer.encode(buffer) } - override val payloadSize: Int + override val bodySize: Int get() = amfContainer.size } \ No newline at end of file diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/video/ExtendedVideoTag.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/video/ExtendedVideoTag.kt new file mode 100644 index 000000000..ae325207b --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/video/ExtendedVideoTag.kt @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2023 Thibault B. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.thibaultbee.streampack.internal.muxers.flv.tags.video + +import android.media.MediaFormat +import android.os.Build +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.FlvTag +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.TagType +import io.github.thibaultbee.streampack.internal.utils.av.FourCCs +import io.github.thibaultbee.streampack.internal.utils.av.video.hevc.HEVCDecoderConfigurationRecord +import io.github.thibaultbee.streampack.internal.utils.extensions.put +import io.github.thibaultbee.streampack.internal.utils.extensions.putInt24 +import io.github.thibaultbee.streampack.internal.utils.extensions.removeStartCode +import io.github.thibaultbee.streampack.internal.utils.extensions.startCodeSize +import io.github.thibaultbee.streampack.logger.Logger +import io.github.thibaultbee.streampack.utils.TAG +import java.io.IOException +import java.nio.ByteBuffer + +class ExtendedVideoTag( + pts: Long, + private val buffers: List, + private val isKeyFrame: Boolean, + private val packetType: PacketType, + private val mimeType: String +) : + FlvTag(pts, TagType.VIDEO) { + constructor( + pts: Long, + buffer: ByteBuffer, + isKeyFrame: Boolean, + packetType: PacketType, + mimeType: String + ) : this(pts, listOf(buffer), isKeyFrame, packetType, mimeType) + + init { + require(isSupportedCodec(mimeType)) { + "Only AV1, VP9 and HEVC are supported" + } + if (mimeType == MediaFormat.MIMETYPE_VIDEO_HEVC) { + if (packetType == PacketType.SEQUENCE_START) { + require(buffers.size == 3) { "Both SPS, PPS and VPS are expected in sequence mode" } + } else { + require(buffers.size == 1) { "Only one buffer is expected for raw frame" } + } + } + } + + override fun writeTagHeader(buffer: ByteBuffer) { + val frameType = if (isKeyFrame) { + FrameType.KEY + } else { + FrameType.INTER + } + + // ExVideoTagHeader + buffer.put( + (1 shl 7) or // IsExHeader + (frameType.value shl 4) or // Frame Type + packetType.value // PacketType + ) + buffer.putInt(FourCCs.fromMimeType(mimeType).value.code) // Video FourCC + } + + override val tagHeaderSize = VIDEO_TAG_HEADER_SIZE + + override fun writeBody(buffer: ByteBuffer) { + Logger.e(TAG, "PacketType $packetType") + when (packetType) { + PacketType.META_DATA -> { + throw NotImplementedError("PacketType $packetType is not supported for $mimeType") + } + + PacketType.SEQUENCE_END -> { + // signals end of sequence + } + + else -> { + when (mimeType) { + MediaFormat.MIMETYPE_VIDEO_HEVC -> { + when (packetType) { + PacketType.SEQUENCE_START -> + HEVCDecoderConfigurationRecord.fromParameterSets(buffers) + .write(buffer) + + PacketType.CODED_FRAMES_X, + PacketType.CODED_FRAMES -> { + if (packetType == PacketType.CODED_FRAMES) { + buffer.putInt24(0) // TODO: CompositionTime + } + writeNalu(buffer) + } + + else -> throw IOException("PacketType $packetType is not supported for $mimeType") + } + } + + else -> { + throw IOException("Mimetype $mimeType is not supported") + } + } + } + } + } + + private fun writeNalu(buffer: ByteBuffer) { + val noStartCodeBuffer = buffers[0].removeStartCode() + buffer.putInt(noStartCodeBuffer.remaining()) + buffer.put(noStartCodeBuffer) + } + + override val bodySize = computeBodySize() + + private fun computeBodySize(): Int { + return when (packetType) { + PacketType.META_DATA -> { + throw NotImplementedError("PacketType $packetType is not supported for $mimeType") + } + + PacketType.SEQUENCE_END -> { + 0 + } + + else -> { + when (mimeType) { + MediaFormat.MIMETYPE_VIDEO_HEVC -> { + when (packetType) { + PacketType.SEQUENCE_START -> + HEVCDecoderConfigurationRecord.getSize( + buffers[0], + buffers[1], + buffers[2] + ) + + PacketType.CODED_FRAMES_X, + PacketType.CODED_FRAMES -> { + (if (packetType == PacketType.CODED_FRAMES) 3 else 0) + getNaluSize() + } + + else -> throw IOException("PacketType $packetType is not supported for $mimeType") + } + } + + else -> { + throw IOException("Mimetype $mimeType is not supported") + } + } + } + } + } + + private fun getNaluSize(): Int { + return buffers[0].remaining() - buffers[0].startCodeSize + 4 // Replace start code with annex B + } + + companion object { + private const val VIDEO_TAG_HEADER_SIZE = 5 + + fun isSupportedCodec(mimeType: String): Boolean { + val isAV1 = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + (mimeType == MediaFormat.MIMETYPE_VIDEO_AV1) + } else { + false + } + return (isAV1) || (mimeType == MediaFormat.MIMETYPE_VIDEO_VP9) || (mimeType == MediaFormat.MIMETYPE_VIDEO_HEVC) + } + } +} + +enum class PacketType(val value: Int) { + SEQUENCE_START(0), // Sequence Start + CODED_FRAMES(1), + SEQUENCE_END(2), + CODED_FRAMES_X(3), + META_DATA(4), + MPEG2_TS_SEQUENCE_START(5); + + val avcPacketType: AVCPacketType + get() { + return if (this == SEQUENCE_START) { + AVCPacketType.SEQUENCE_HEADER + } else if ((this == CODED_FRAMES) || (this == CODED_FRAMES_X)) { + AVCPacketType.NALU + } else if (this == SEQUENCE_END) { + AVCPacketType.END_OF_SEQUENCE + } else { + throw UnsupportedOperationException("Unsupported type $this for AVCPacketType") + } + } +} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/video/VideoTag.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/video/VideoTag.kt new file mode 100644 index 000000000..7d3e87dd4 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/video/VideoTag.kt @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2022 Thibault B. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.thibaultbee.streampack.internal.muxers.flv.tags.video + +import android.media.MediaFormat +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.FlvTag +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.TagType +import io.github.thibaultbee.streampack.internal.utils.av.video.avc.AVCDecoderConfigurationRecord +import io.github.thibaultbee.streampack.internal.utils.extensions.put +import io.github.thibaultbee.streampack.internal.utils.extensions.putInt24 +import io.github.thibaultbee.streampack.internal.utils.extensions.removeStartCode +import io.github.thibaultbee.streampack.internal.utils.extensions.startCodeSize +import java.io.IOException +import java.nio.ByteBuffer + +/** + * @param packetType 0: AVC sequence header, 1: AVC NALU, 2: AVC end of sequence. Using `PacketType` instead of `AVCPacketType` to simplify as the first 3 values are the same. + */ +class VideoTag( + pts: Long, + private val buffers: List, + private val isKeyFrame: Boolean, + private val packetType: AVCPacketType?, + private val mimeType: String +) : + FlvTag(pts, TagType.VIDEO) { + constructor( + pts: Long, + buffer: ByteBuffer, + isKeyFrame: Boolean, + packetType: AVCPacketType?, + mimeType: String + ) : this(pts, listOf(buffer), isKeyFrame, packetType, mimeType) + + private val codecID = CodecID.fromMimeType(mimeType) + + init { + require(isSupportedCodec(mimeType)) { + "Only H263 and H264 are supported" + } + if (mimeType == MediaFormat.MIMETYPE_VIDEO_AVC) { + requireNotNull(packetType) { "AVC packet type is required for H264" } + if (packetType == AVCPacketType.SEQUENCE_HEADER) { + require(buffers.size == 2) { "Both SPS and PPS are expected in sequence mode" } + } else { + require(buffers.size == 1) { "Only one buffer is expected for raw frame" } + } + } + } + + override fun writeTagHeader(buffer: ByteBuffer) { + val frameType = if (isKeyFrame) { + FrameType.KEY + } else { + FrameType.INTER + } + buffer.put( + (frameType.value shl 4) or // Frame Type + (codecID.value) // CodecID + ) + if (mimeType == MediaFormat.MIMETYPE_VIDEO_AVC) { + buffer.put(packetType!!.value) // AVC sequence header or NALU + buffer.putInt24(0) // TODO: CompositionTime + } + } + + override val tagHeaderSize = computeHeaderSize() + + private fun computeHeaderSize(): Int { + var size = VIDEO_TAG_HEADER_SIZE + if (mimeType == MediaFormat.MIMETYPE_VIDEO_AVC) { + size += 4 // AVCPacketType & CompositionTime + } + return size + } + + override fun writeBody(buffer: ByteBuffer) { + when (packetType) { + AVCPacketType.END_OF_SEQUENCE -> { + // signals end of sequence + } + + else -> { + when (mimeType) { + MediaFormat.MIMETYPE_VIDEO_AVC -> { + when (packetType) { + AVCPacketType.SEQUENCE_HEADER -> + AVCDecoderConfigurationRecord.fromParameterSets( + buffers[0], + buffers[1] + ).write(buffer) + + AVCPacketType.NALU -> writeNalu(buffer) + else -> throw IOException("PacketType $packetType is not supported for $mimeType") + } + } + + else -> { + throw IOException("Mimetype $mimeType is not supported") + } + } + } + } + } + + private fun writeNalu(buffer: ByteBuffer) { + val noStartCodeBuffer = buffers[0].removeStartCode() + buffer.putInt(noStartCodeBuffer.remaining()) + buffer.put(noStartCodeBuffer) + } + + override val bodySize = computeBodySize() + + private fun computeBodySize(): Int { + return when (packetType) { + AVCPacketType.END_OF_SEQUENCE -> { + 0 + } + + else -> { + when (mimeType) { + MediaFormat.MIMETYPE_VIDEO_AVC -> { + when (packetType) { + AVCPacketType.SEQUENCE_HEADER -> + AVCDecoderConfigurationRecord.getSize( + buffers[0], + buffers[1] + ) + + else -> getNaluSize() + } + } + + else -> { + throw IOException("Mimetype $mimeType is not supported") + } + } + } + } + } + + private fun getNaluSize(): Int { + return buffers[0].remaining() - buffers[0].startCodeSize + 4 // Replace start code with annex B + } + + companion object { + private const val VIDEO_TAG_HEADER_SIZE = 1 + + fun isSupportedCodec(mimeType: String) = + (mimeType == MediaFormat.MIMETYPE_VIDEO_AVC) || (mimeType == MediaFormat.MIMETYPE_VIDEO_H263) + } + +} + +enum class FrameType(val value: Int) { + KEY(1), + INTER(2), + DISPOSABLE_INTER(3), + GENERATED_KEY(4), + INFO_COMMAND(5) +} + +enum class CodecID(val value: Int) { + SORENSON_H263(2), + SCREEN_1(3), + VP6(4), + VP6_ALPHA(5), + SCREEN_2(6), + AVC(7); + + fun toMimeType() = when (this) { + SORENSON_H263 -> MediaFormat.MIMETYPE_VIDEO_H263 + AVC -> MediaFormat.MIMETYPE_VIDEO_AVC + else -> throw IOException("MimeType is not supported: $this") + } + + companion object { + fun fromMimeType(mimeType: String) = when (mimeType) { + MediaFormat.MIMETYPE_VIDEO_H263 -> SORENSON_H263 + MediaFormat.MIMETYPE_VIDEO_AVC -> AVC + else -> throw IOException("MimeType is not supported: $mimeType") + } + } +} + +enum class AVCPacketType(val value: Int) { + SEQUENCE_HEADER(0), + NALU(1), + END_OF_SEQUENCE(2) +} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/video/VideoTagFactory.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/video/VideoTagFactory.kt new file mode 100644 index 000000000..83e683a20 --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/video/VideoTagFactory.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 Thibault B. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.thibaultbee.streampack.internal.muxers.flv.tags.video + +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.FlvTag +import java.nio.ByteBuffer + +class VideoTagFactory( + private val pts: Long, + private val buffers: List, + private val isKeyFrame: Boolean, + private val packetType: PacketType, + private val mimeType: String +) { + constructor( + pts: Long, + buffer: ByteBuffer, + isKeyFrame: Boolean, + packetType: PacketType, + mimeType: String + ) : this(pts, listOf(buffer), isKeyFrame, packetType, mimeType) + + fun build(): FlvTag { + return if (ExtendedVideoTag.isSupportedCodec(mimeType)) { + ExtendedVideoTag(pts, buffers, isKeyFrame, packetType, mimeType) + } else { + // Packet type if only for AVC + VideoTag(pts, buffers, isKeyFrame, packetType.avcPacketType, mimeType) + } + } +} diff --git a/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/FourCC.kt b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/FourCC.kt new file mode 100644 index 000000000..01bdc5b8b --- /dev/null +++ b/core/src/main/java/io/github/thibaultbee/streampack/internal/utils/av/FourCC.kt @@ -0,0 +1,33 @@ +package io.github.thibaultbee.streampack.internal.utils.av + +import android.media.MediaFormat +import android.os.Build + + +enum class FourCCs(val value: FourCC) { + AV1( + FourCC( + 'a', 'v', '0', '1', if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + MediaFormat.MIMETYPE_VIDEO_AV1 + } else { + null + } + ) + ), + VP9(FourCC('v', 'p', '0', '9', MediaFormat.MIMETYPE_VIDEO_VP9)), + HEVC(FourCC('h', 'v', 'c', '1', MediaFormat.MIMETYPE_VIDEO_HEVC)); + + companion object { + fun fromMimeType(mimeType: String) = + values().first { it.value.mimeType == mimeType } + } +} + +data class FourCC(val a: Char, val b: Char, val c: Char, val d: Char, val mimeType: String?) { + val code: Int + get() = (a.code shl 24) or (b.code shl 16) or (c.code shl 8) or d.code + + override fun toString(): String { + return "$a$b$c$d" + } +} \ No newline at end of file diff --git a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/OnMetadataTest.kt b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/OnMetadataTest.kt similarity index 98% rename from core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/OnMetadataTest.kt rename to core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/OnMetadataTest.kt index 5aae2b8bc..bb203782c 100644 --- a/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/flv/packet/OnMetadataTest.kt +++ b/core/src/test/java/io/github/thibaultbee/streampack/internal/muxers/flv/tags/OnMetadataTest.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.github.thibaultbee.streampack.internal.muxers.flv.packet +package io.github.thibaultbee.streampack.internal.muxers.flv.tags import android.util.Size import io.github.thibaultbee.streampack.data.VideoConfig diff --git a/extensions/rtmp/build.gradle b/extensions/rtmp/build.gradle index d85002c39..ba7d06103 100644 --- a/extensions/rtmp/build.gradle +++ b/extensions/rtmp/build.gradle @@ -8,7 +8,7 @@ ext { dependencies { implementation project(':core') - implementation 'video.api:rtmpdroid:1.0.5' + implementation 'video.api:rtmpdroid:1.1.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' implementation "androidx.core:core-ktx:${androidx_core_version}" diff --git a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/internal/endpoints/RtmpProducer.kt b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/internal/endpoints/RtmpProducer.kt index e9cedc81e..eb5a70df3 100644 --- a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/internal/endpoints/RtmpProducer.kt +++ b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/internal/endpoints/RtmpProducer.kt @@ -43,12 +43,28 @@ class RtmpProducer( private val audioPacketQueue = mutableListOf() + /** + * Sets/gets supported video codecs. + */ + var supportedVideoCodecs: List + get() = socket.supportedVideoCodecs + set(value) { + socket.supportedVideoCodecs = value + } + override fun configure(config: Int) { } override suspend fun connect(url: String) { - if (!url.startsWith(RTMP_PREFIX)) { - throw InvalidParameterException("URL must start with $RTMP_PREFIX") + if (!url.startsWith(RTMP_PREFIX) && + !url.startsWith(RTMPS_PREFIX) && + !url.startsWith(RTMPT_PREFIX) && + !url.startsWith(RTMPE_PREFIX) && + !url.startsWith(RTMFP_PREFIX) && + !url.startsWith(RTMPTE_PREFIX) && + !url.startsWith(RTMPTS_PREFIX) + ) { + throw InvalidParameterException("URL must start with $RTMP_PREFIX, $RTMPS_PREFIX, $RTMPT_PREFIX, $RTMPE_PREFIX, $RTMFP_PREFIX, $RTMPTE_PREFIX or $RTMPTS_PREFIX") } withContext(coroutineDispatcher) { try { @@ -145,5 +161,23 @@ class RtmpProducer( companion object { private const val RTMP_SCHEME = "rtmp" private const val RTMP_PREFIX = "$RTMP_SCHEME://" + + private const val RTMPS_SCHEME = "rtmps" + private const val RTMPS_PREFIX = "$RTMPS_SCHEME://" + + private const val RTMPT_SCHEME = "rtmpt" + private const val RTMPT_PREFIX = "$RTMPT_SCHEME://" + + private const val RTMPE_SCHEME = "rtmpe" + private const val RTMPE_PREFIX = "$RTMPE_SCHEME://" + + private const val RTMFP_SCHEME = "rtmfp" + private const val RTMFP_PREFIX = "$RTMFP_SCHEME://" + + private const val RTMPTE_SCHEME = "rtmpte" + private const val RTMPTE_PREFIX = "$RTMPTE_SCHEME://" + + private const val RTMPTS_SCHEME = "rtmpts" + private const val RTMPTS_PREFIX = "$RTMPTS_SCHEME://" } } \ No newline at end of file diff --git a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/AudioOnlyRtmpLiveStreamer.kt b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/AudioOnlyRtmpLiveStreamer.kt index caca10ebd..2b194e93e 100644 --- a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/AudioOnlyRtmpLiveStreamer.kt +++ b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/AudioOnlyRtmpLiveStreamer.kt @@ -18,6 +18,7 @@ package io.github.thibaultbee.streampack.ext.rtmp.streamers import android.content.Context import io.github.thibaultbee.streampack.ext.rtmp.internal.endpoints.RtmpProducer import io.github.thibaultbee.streampack.internal.muxers.flv.FlvMuxer +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.video.ExtendedVideoTag import io.github.thibaultbee.streampack.listeners.OnConnectionListener import io.github.thibaultbee.streampack.listeners.OnErrorListener import io.github.thibaultbee.streampack.streamers.live.BaseAudioOnlyLiveStreamer @@ -39,4 +40,17 @@ class AudioOnlyRtmpLiveStreamer( endpoint = RtmpProducer(hasAudio = true, hasVideo = false), initialOnErrorListener = initialOnErrorListener, initialOnConnectionListener = initialOnConnectionListener -) +) { + private val rtmpProducer = endpoint as RtmpProducer + override suspend fun connect(url: String) { + require(videoConfig != null) { + "Video config must be set before connecting to send the video codec in the connect message" + } + val codecMimeType = videoConfig!!.mimeType + if (ExtendedVideoTag.isSupportedCodec(codecMimeType)) { + rtmpProducer.supportedVideoCodecs = listOf(codecMimeType) + } + rtmpProducer.supportedVideoCodecs = listOf(videoConfig!!.mimeType) + super.connect(url) + } +} diff --git a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/CameraRtmpLiveStreamer.kt b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/CameraRtmpLiveStreamer.kt index 4ada45552..0d362c8ed 100644 --- a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/CameraRtmpLiveStreamer.kt +++ b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/CameraRtmpLiveStreamer.kt @@ -18,6 +18,7 @@ package io.github.thibaultbee.streampack.ext.rtmp.streamers import android.content.Context import io.github.thibaultbee.streampack.ext.rtmp.internal.endpoints.RtmpProducer import io.github.thibaultbee.streampack.internal.muxers.flv.FlvMuxer +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.video.ExtendedVideoTag import io.github.thibaultbee.streampack.listeners.OnConnectionListener import io.github.thibaultbee.streampack.listeners.OnErrorListener import io.github.thibaultbee.streampack.streamers.live.BaseCameraLiveStreamer @@ -42,4 +43,17 @@ class CameraRtmpLiveStreamer( endpoint = RtmpProducer(hasAudio = enableAudio, hasVideo = true), initialOnErrorListener = initialOnErrorListener, initialOnConnectionListener = initialOnConnectionListener -) +) { + private val rtmpProducer = endpoint as RtmpProducer + override suspend fun connect(url: String) { + require(videoConfig != null) { + "Video config must be set before connecting to send the video codec in the connect message" + } + val codecMimeType = videoConfig!!.mimeType + if (ExtendedVideoTag.isSupportedCodec(codecMimeType)) { + rtmpProducer.supportedVideoCodecs = listOf(codecMimeType) + } + rtmpProducer.supportedVideoCodecs = listOf(videoConfig!!.mimeType) + super.connect(url) + } +} diff --git a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/ScreenRecorderRtmpLiveStreamer.kt b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/ScreenRecorderRtmpLiveStreamer.kt index 3a5c4a246..5d162886f 100644 --- a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/ScreenRecorderRtmpLiveStreamer.kt +++ b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/streamers/ScreenRecorderRtmpLiveStreamer.kt @@ -19,6 +19,7 @@ import android.app.Service import android.content.Context import io.github.thibaultbee.streampack.ext.rtmp.internal.endpoints.RtmpProducer import io.github.thibaultbee.streampack.internal.muxers.flv.FlvMuxer +import io.github.thibaultbee.streampack.internal.muxers.flv.tags.video.ExtendedVideoTag import io.github.thibaultbee.streampack.listeners.OnConnectionListener import io.github.thibaultbee.streampack.listeners.OnErrorListener import io.github.thibaultbee.streampack.streamers.bases.BaseScreenRecorderStreamer @@ -46,4 +47,17 @@ class ScreenRecorderRtmpLiveStreamer( endpoint = RtmpProducer(hasAudio = enableAudio, hasVideo = true), initialOnErrorListener = initialOnErrorListener, initialOnConnectionListener = initialOnConnectionListener -) \ No newline at end of file +) { + private val rtmpProducer = endpoint as RtmpProducer + override suspend fun connect(url: String) { + require(videoConfig != null) { + "Video config must be set before connecting to send the video codec in the connect message" + } + val codecMimeType = videoConfig!!.mimeType + if (ExtendedVideoTag.isSupportedCodec(codecMimeType)) { + rtmpProducer.supportedVideoCodecs = listOf(codecMimeType) + } + rtmpProducer.supportedVideoCodecs = listOf(videoConfig!!.mimeType) + super.connect(url) + } +} \ No newline at end of file