RtmpClient issue with Mona Server
Closed this issue · 2 comments
I'm trying to do RTMP stream from a surface using the code below (using com.github.pedroSG94.RootEncoder:library:2.5.4). The encoder gets input from the surface (that part of the code is excluded). When encoded frame information is available, the drain method is called, and this is where I'm sending the data using RtmpClient.
Issue:
Code -> nginx RTMP server -> VLC: works
Code -> MonaTiny RTMP server -> VLC: fails
OBS -> MonaTiny RTMP server -> VLC: works
OBS -> nginx RTMP server -> VLC: works
Detail:
The code works when I use nginx RTMP server, the video feed is viewable in VLC. But the same code fails with Mona (Tiny) Server. Only the first frame is displayed as a static image instead of the video stream in VLC.
To isolate the issue, I tested streaming from OBS Studio to MonaTiny, it displayed fine in VLC.
Then I checked out the latest master branch code of Root Encoder and the sample app worked without any issues with MonaTiny.
I've also tested my code below to write mp4 video instead of RTMP, and that worked too.
So I'm probably missing something that's specific to MonaTiny, while setting up the RtmpClient. I've looked at many of the past open/closed issues of Root Encoder, and tried several configuration options, but no luck so far.
Nothing unusual in Logcat either.
public class StreamingVideoEncoder extends MediaEncoder implements IVideoEncoder {
private final int mWidth = 640;
private final int mHeight = 480;
private static final int FRAME_RATE = 24;
private static final float BPP = 0.5f;
private static final String MIME_TYPE = "video/avc";
private Surface mSurface;
private final RtmpClient rtmpClient;
private boolean rtmpConnected = false;
private boolean isSpsPpsSent = false;
private ByteBuffer sps = null;
private ByteBuffer pps = null;
public StreamingVideoEncoder(final String rtmpUrl) {
this.rtmpClient = new RtmpClient(new ConnectChecker() {
@Override
public void onAuthSuccess() {}
@Override
public void onAuthError() {}
@Override
public void onDisconnect() {
rtmpConnected = false;
isSpsPpsSent = false;
}
@Override
public void onConnectionFailed(@NonNull String s) {
rtmpConnected = false;
isSpsPpsSent = false;
Log.d(TAG, "RTMP connection failed: " + s);
}
@Override
public void onConnectionSuccess() {
rtmpConnected = true;
Log.d(TAG, "RTMP connected");
}
@Override
public void onConnectionStarted(@NonNull String s) {
rtmpConnected = false;
Log.d(TAG, "RTMP connection started");
}
});
rtmpClient.setVideoResolution(mWidth, mHeight);
rtmpClient.setFps(FRAME_RATE);
rtmpClient.setVideoCodec(VideoCodec.H264);
rtmpClient.setOnlyVideo(true);
rtmpClient.setLogs(true);
rtmpClient.connect(rtmpUrl, true);
}
@Override
protected void prepare() throws IOException {
final int bitRate = calcBitRate();
final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline);
format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel31);
mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mSurface = mMediaCodec.createInputSurface();
mMediaCodec.start();
}
private int calcBitRate() {
final int bitrate = (int) (BPP * FRAME_RATE * mWidth * mHeight);
Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f));
return bitrate;
}
@Override
protected void drain() {
if (mMediaCodec == null) return;
ByteBuffer[] encoderOutputBuffers = mMediaCodec.getOutputBuffers();
int encoderStatus, count = 0;
if (rtmpClient == null || !rtmpConnected) {
return;
}
while (mIsCapturing) {
encoderStatus = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
if (!mIsEOS) {
if (++count > 5)
break;
}
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
encoderOutputBuffers = mMediaCodec.getOutputBuffers();
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat format = mMediaCodec.getOutputFormat();
if (!isSpsPpsSent) {
sps = format.getByteBuffer("csd-0");
pps = format.getByteBuffer("csd-1");
if (sps != null && pps != null) {
rtmpClient.setVideoInfo(sps, pps, null);
isSpsPpsSent = true;
}
}
} else if (encoderStatus < 0) {
if (DEBUG) Log.w(TAG, "drain:unexpected result from encoder#dequeueOutputBuffer: " + encoderStatus);
} else {
final ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
}
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
mBufferInfo.size = 0;
}
if (mBufferInfo.size != 0) {
count = 0;
if (rtmpConnected) {
mBufferInfo.presentationTimeUs = getPTSUs();
rtmpClient.sendVideo(encodedData, mBufferInfo);
prevOutputPTSUs = mBufferInfo.presentationTimeUs;
}
}
mMediaCodec.releaseOutputBuffer(encoderStatus, false);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
mIsCapturing = false;
break; // Exit the loop on EOS
}
}
}
}
protected long getPTSUs() {
long result = System.nanoTime() / 1000L;
// presentationTimeUs should be monotonic
if (result < prevOutputPTSUs)
result = (prevOutputPTSUs - result) + result;
return result;
}
}
Really difficult to say.
Do you have any logs from the server or VLC? Did you test with other player like ffplay?
Are you using version 2.5.4 to discard problems with RtmpClient class?
Also, make sure that MonaTiny support only video streams. You can compile the app example and force only video:
getStreamClient().setOnlyVideo(true)
I've found the issue in my code after debugging a little more.
My presentation ts calculation was wrong, the following modification fixed it.
protected long getPTSUs() {
long currentTime = System.currentTimeMillis() * 1000;
if (firstFrameTimestamp == -1) {
firstFrameTimestamp = currentTime;
return 0;
}
return currentTime - firstFrameTimestamp;
}
Thanks for the quick reply!