pedroSG94 / RootEncoder

RootEncoder for Android (rtmp-rtsp-stream-client-java) is a stream encoder to push video/audio to media servers using protocols RTMP, RTSP, SRT and UDP with all code written in Java/Kotlin
Apache License 2.0
2.57k stars 778 forks source link

Rotation issue for RTMP stream in portrait mode (from surface, off-screen) #1647

Closed FahimHT closed 4 days ago

FahimHT commented 5 days ago

I'm doing RTMP stream from a surface. The problem is, when the phone is in portrait mode, I can't figure out how to rotate the stream by 90 degrees.

Currently I have an encoder, I'm passing its input surface to the capture request builder. It works in landscape mode.

// Note that if I swap width, height below to set portrait mode, capture session creation fails with an exception
MediaFormat videoFormat = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
videoEncoder.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Surface streamSurface = videoEncoder.createInputSurface();
//..
CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
builder.addTarget(streamSurface);

I've checked all previously reported issues here related to rotation, but either couldn't make the solutions from those work, or they weren't applicable since I'm not using a preview display.

Tried below code to rotate using OpenGL before passing to the encoder, but couldn't make it work. I'm not sure if this is the proper way, but it throws an exception in r.initGl() with message could not compile shader 35633 (tested on multiple phones).

CameraRender r = new CameraRender();
r.setRotation(90);
r.initGl(640, 480, recordingService, 0,0); // Tried 480, 640 too as I'm not sure which orientation this surface should be
r.draw();
streamSurface = r.getSurface();
videoEncoder.setInputSurface(streamSurface);
//..
CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
builder.addTarget(streamSurface);

Tried OpenGlView too but since I'm recording off-screen, surfaceCreated() is never invoked.

OpenGlView view = new OpenGlView(recordingService);
view.setRotation(90);
view.setStreamRotation(90);
view.addMediaCodecSurface(videoEncoder.createInputSurface());
view.getHolder().addCallback(new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        streamSurface = view.getSurface();
        // ..
        CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
        builder.addTarget(streamSurface);
    }
});
view.setForceRender(true);
view.start();

I know that if I record to file, I can set media-recorder hint to rotate the output file, but that doesn't apply for streaming.

videoFormat.setInteger(MediaFormat.KEY_ROTATION, 90) doesn't work since according to doc it'll only work if I use an output surface, but then I'll have to read each frame from the output surface and convert those in order to be able to stream. This seems inefficient as instead of using:

@Override
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo bufferInfo) { //.. }

I'll then have to listen for each frame of that output surface and process those.

Due to other requirements I can't directly stream from Camera2 using Root Encoder.

Any ideas on how to proceed?

pedroSG94 commented 5 days ago

Hello,

In your case you only can use GlStreamInterface. Code example:

val glInterface = GlStreamInterface(context)

/**start*/
glInterface.setEncoderSize(width, height)
glInterface.setIsPortrait(isPortrait) //should be used, by default false
glInterface.setCameraOrientation(rotation) //should be used, by default 0, you can use this value to rotate on fly
glInterface.forceOrientation(videoSource.getOrientationConfig()) //optional, force landscape or portrait no matter the rotation value. In most of cases you can ignore it.
glInterface.start()
 //Send glInterface to the camera, the camera render the surface of glInterface
builder.addTarget(Surface(glInterface.surfaceTexture))
//Add surface of videoEncoder to glInteface, the data received in the glInterface.surfaceTexture is copied into videoEncoder.inputSurface and you can use opengl to modify it as you want
glInterface.addMediaCodecSurface(videoEncoder.inputSurface)

/**stop*/
glInterface.removeMediaCodecSurface()
glInterface.stop()

The code example is from this methods: https://github.com/pedroSG94/RootEncoder/blob/master/library/src/main/java/com/pedro/library/base/StreamBase.kt#L110 https://github.com/pedroSG94/RootEncoder/blob/master/library/src/main/java/com/pedro/library/base/StreamBase.kt#L490

https://github.com/pedroSG94/RootEncoder/blob/master/library/src/main/java/com/pedro/library/base/StreamBase.kt#L504

This allow you rotate the image, you can even use filters or add a preview on fly when you want. You have a rotation filter if it is necessary

FahimHT commented 4 days ago

Thank you so much! Stream is now working with correct orientation.