pili-engineering / PLDroidMediaStreaming

PLDroidMediaStreaming 是七牛推出的一款适用于 Android 平台的推流 SDK,支持 RTMP 推流,h.264 和 AAC 编码,硬编、软编支持。具有丰富的数据和状态回调,方便用户根据自己的业务定制化开发。具有直播场景下的重要功能,如:美颜、背景音乐、水印等功能。PLDroidMediaStreaming 是现在目前重点维护的版本,自带采集模块也支持用户自己做采集端。
https://github.com/pili-engineering/PLDroidMediaStreaming/wiki
Apache License 2.0
1.45k stars 460 forks source link

Square Aspect ratio or Can we get the source code to make changes ourselves? #16

Open gouravd opened 9 years ago

gouravd commented 9 years ago

Can we get the source code for the library.

I am looking for 1:1 (Square) aspect ratio for our app. Could you implement it?

jpxiong commented 9 years ago

1:1 aspect ratio maybe stretch or compress the camera preview. Is the following snippet what you expected:

    <com.pili.pldroid.streaming.widget.AspectFrameLayout
        android:id="@+id/cameraPreview_afl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" >

        <android.opengl.GLSurfaceView
            android:id="@+id/cameraPreview_surfaceView"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_gravity="center" />
    </com.pili.pldroid.streaming.widget.AspectFrameLayout>
gouravd commented 9 years ago

The scaling and the cropping has to be handled by your code (may be the same way you handle 4:3 ratio) else the output will be stretched or compressed.

I am currently using open source Kickflip sdk and I modified certain part of their code via matrix transformation for proper square aspect ratio. On Aug 23, 2015 7:45 AM, "jpxiong" notifications@github.com wrote:

1:1 aspect ratio maybe stretch or compress the camera preview. Is the following snippet what you expected:

<com.pili.pldroid.streaming.widget.AspectFrameLayout
    android:id="@+id/cameraPreview_afl"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true" >

    <android.opengl.GLSurfaceView
        android:id="@+id/cameraPreview_surfaceView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center" />
</com.pili.pldroid.streaming.widget.AspectFrameLayout>

— Reply to this email directly or view it on GitHub https://github.com/pili-engineering/PLDroidCameraStreaming/issues/16#issuecomment-133772417 .

jpxiong commented 9 years ago

OK. You just need the 1:1 (Square) aspect ratio for the layout , but not the GLSurfaceView. Right ?

gouravd commented 9 years ago

My requirement is that the output video should be Square format and the preview should be square as well. So I guess it has to be implemented in your code to properly handle 1:1 aspect and exposed via your API, something like CameraStreamingSetting.PREVIEW_SIZE_RATIO.RATIO_SQUARE

In Kickflip, they have a FullScreenRect.java (https://github.com/Kickflip/kickflip-android-sdk/blob/master/sdk/src/main/java/io/kickflip/sdk/av/FullFrameRect.java) which accepts a TextTure2DProgram as constructor argument. They have a function called adjustForVerticalVideo() which is called first time camera and preview is ready and called subsequently to adjust ratios when the camera is rotated (from portrait to landscape etc..)

I modified that part of code as follows, for proper scaling into square aspect ratio. The SurfaceVIew in my code resides in a square layout and hence the preview and output are both square.

Matrix.setIdentityM(IDENTITY_MATRIX, 0); float scaleval = isWideScreen ? 1.78f : 1.33f; //if(16:9) then 1.78 scale factor, else if 4:3 then 1.33 scale factor switch (orientation) { case VERTICAL: Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, 1f, 1f); case LANDSCAPE: Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f); break; case UPSIDEDOWN_VERTICAL: Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f); break; case UPSIDEDOWN_LANDSCAPE: Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f); break; }

jpxiong commented 9 years ago

Thanks for your sharing. It's very helpful to me.

jpxiong commented 9 years ago

It seems adjustForVerticalVideo can't work properly .

After i invoked the adjustForVerticalVideo, the preview didn't any change. What did i miss?

public void adjustForVerticalVideo(SCREEN_ROTATION orientation, boolean isWideScreen) {
        synchronized (mDrawLock) {
            Log.i("FullFrameRect", "adjustForVerticalVideo orientation:" + orientation + ",isWideScreen:" + isWideScreen);
            mCorrectVerticalVideo = true;
            requestedOrientation = orientation;
            Matrix.setIdentityM(IDENTITY_MATRIX, 0);
            float scaleval = isWideScreen ? 1.78f : 1.33f; //if(16:9) then 1.78 scale factor, else if 4:3 then 1.33 scale factor
            switch (orientation) {
                case VERTICAL:
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, 1f, 1f);
                case LANDSCAPE:
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f);
                    break;
                case UPSIDEDOWN_VERTICAL:
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f);
                    break;
                case UPSIDEDOWN_LANDSCAPE:
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f);
                    break;
            }
        }
    }
gouravd commented 9 years ago

I remember that I had to change a lot of other stuff as well along with this piece of code. I had to change the section which calls adjustForverticalVideo and the section that handles phone rotation among other things..I also had to comment out a portion of code in the same file fullframerect.java, which

P.S: I am still trying to figure out how to go to a specific time in my commit to retrieve the entire source code tree and then I would be able to tell you what changes I did exactly. Source code is hosted in Microsoft and I guess there is no direct way to browse the entire tree at certain commit. I guess I would have to clone a specific commit to my machine.

gouravd commented 9 years ago

This is how my fullframerect.java looks. Please note that //Matrix.scaleM(texMatrix, 0, 0.316f, 1.0f, 1f); is commented out in drawFrame()

public class FullFrameRect { public static enum SCREEN_ROTATION {LANDSCAPE, VERTICAL, UPSIDEDOWN_LANDSCAPE, UPSIDEDOWN_VERTICAL}

private final Drawable2d mRectDrawable = new Drawable2d(Drawable2d.Prefab.FULL_RECTANGLE);
private Texture2dProgram mProgram;
private final Object mDrawLock = new Object();

private static final int SIZEOF_FLOAT = 4;

private float[] IDENTITY_MATRIX = new float[16];

private static final float TEX_COORDS[] = {
        0.0f, 0.0f,     // 0 bottom left
        1.0f, 0.0f,     // 1 bottom right
        0.0f, 1.0f,     // 2 top left
        1.0f, 1.0f      // 3 top right
};
private static final FloatBuffer TEX_COORDS_BUF = GlUtil.createFloatBuffer(TEX_COORDS);
private static final int TEX_COORDS_STRIDE = 2 * SIZEOF_FLOAT;

private boolean mCorrectHorizontalVideo = false;
private boolean mScaleToFit;
private SCREEN_ROTATION requestedOrientation = SCREEN_ROTATION.VERTICAL;

/**
 * Prepares the object.
 *
 * @param program The program to use.  FullFrameRect takes ownership, and will release
 *                the program when no longer needed.
 */
public FullFrameRect(Texture2dProgram program) {
    mProgram = program;
    Matrix.setIdentityM(IDENTITY_MATRIX, 0);
}

/**
 * Adjust the MVP Matrix to rotate and crop the texture
 * to make Horizontal video appear upright
 */
public void fixAspectRatio(SCREEN_ROTATION orientation, boolean scaleToFit, boolean isWideScreen) {
    synchronized (mDrawLock) {
        mCorrectHorizontalVideo = true;
        mScaleToFit = scaleToFit;
        requestedOrientation = orientation;
        Matrix.setIdentityM(IDENTITY_MATRIX, 0);
        float scaleval = isWideScreen ? 1.78f : 1.33f;
        switch (orientation) {
            case VERTICAL:
                if(scaleToFit)
                {
                    //Matrix.rotateM(IDENTITY_MATRIX, 0, -90, 0f, 0f, 1f);
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, 1f, 1f);
                }
                else {
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, 1f, 1f);
                }
            case LANDSCAPE:
                if (scaleToFit) {
                    //Matrix.rotateM(IDENTITY_MATRIX, 0, 90, 0f, 0f, 1f);
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f);
                } else {
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f);
                }
                break;
            case UPSIDEDOWN_VERTICAL:
                if(scaleToFit)
                {
                    //Matrix.rotateM(IDENTITY_MATRIX, 0, -90, 0f, 0f, 1f);
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f);
                }
                else {
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f);
                }
                break;
            case UPSIDEDOWN_LANDSCAPE:
                if (scaleToFit) {
                    //Matrix.rotateM(IDENTITY_MATRIX, 0, -90, 0f, 0f, 1f);
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f);
                } else {
                    Matrix.scaleM(IDENTITY_MATRIX, 0, 1f, scaleval, 1f);
                }
                break;
        }
    }
}

/**
 * Releases resources.
 */
public void release() {
    if (mProgram != null) {
        mProgram.release();
        mProgram = null;
    }
}

/**
 * Returns the program currently in use.
 */
public Texture2dProgram getProgram() {
    return mProgram;
}

/**
 * Changes the program.  The previous program will be released.
 */
public void changeProgram(Texture2dProgram program) {
    mProgram.release();
    mProgram = program;
}

/**
 * Creates a texture object suitable for use with drawFrame().
 */
public int createTextureObject() {
    return mProgram.createTextureObject();
}

/**
 * Draws a viewport-filling rect, texturing it with the specified texture object.
 */
public void drawFrame(int textureId, float[] texMatrix) {
    // Use the identity matrix for MVP so our 2x2 FULL_RECTANGLE covers the viewport.
    synchronized (mDrawLock) {
        if (mCorrectHorizontalVideo && !mScaleToFit && (requestedOrientation == SCREEN_ROTATION.LANDSCAPE || requestedOrientation == SCREEN_ROTATION.UPSIDEDOWN_LANDSCAPE)) {
            //Matrix.scaleM(texMatrix, 0, 0.316f, 1.0f, 1f);
        }
        mProgram.draw(IDENTITY_MATRIX, mRectDrawable.getVertexArray(), 0,
                mRectDrawable.getVertexCount(), mRectDrawable.getCoordsPerVertex(),
                mRectDrawable.getVertexStride(),
                texMatrix, TEX_COORDS_BUF, textureId, TEX_COORDS_STRIDE);
    }
}

/**
 * Pass touch event down to the
 * texture's shader program
 *
 * @param ev
 */
public void handleTouchEvent(MotionEvent ev) {
    mProgram.handleTouchEvent(ev);
}

}

gouravd commented 9 years ago

This is how my SensonChangedListener looks like (BroadcastActivity.java). mBroadcaster.fixAspectRatio calls fixAspectRatio in CameraEncoder.

private SensorEventListener mOrientationListener = new SensorEventListener() { final int SENSOR_CONFIRMATION_THRESHOLD = 5; int[] confirmations = new int[2]; int orientation = -1;

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (activity != null && activity.findViewById(R.id.rotateDeviceHint) != null) {
            //Log.i(TAG, "Sensor " + event.values[1]);
            if (event.values[1] > 10 || event.values[1] < -10) {
                // Sensor noise. Ignore.
            } else if (event.values[1] < 5.5 && event.values[1] > -5.5) {
                // Landscape
                if (orientation != 1 && readingConfirmed(1)) {
                    if (mBroadcaster.getSessionConfig().getFixAspectRatio()) {
                        if (event.values[0] > 0) {
                            mBroadcaster.fixAspectRatio(FullFrameRect.SCREEN_ROTATION.LANDSCAPE);
                        } else {
                            mBroadcaster.fixAspectRatio(FullFrameRect.SCREEN_ROTATION.UPSIDEDOWN_LANDSCAPE);
                        }
                    } else {
                        activity.findViewById(R.id.rotateDeviceHint).setVisibility(View.VISIBLE);
                    }
                    orientation = 1;
                }
            } else if (event.values[1] > 7.5 || event.values[1] < -7.5) {
                // Portrait
                if (orientation != 0 && readingConfirmed(0)) {
                    if (mBroadcaster.getSessionConfig().getFixAspectRatio()) {
                        if (event.values[1] > 0) {
                            mBroadcaster.fixAspectRatio(FullFrameRect.SCREEN_ROTATION.VERTICAL);
                        } else {
                            mBroadcaster.fixAspectRatio(FullFrameRect.SCREEN_ROTATION.UPSIDEDOWN_VERTICAL);
                        }
                    } else {
                        activity.findViewById(R.id.rotateDeviceHint).setVisibility(View.GONE);
                    }
                    orientation = 0;
                }
            }
        }
    }

    /**
     * Determine if a sensor reading is trustworthy
     * based on a series of consistent readings
     */
    private boolean readingConfirmed(int orientation) {
        confirmations[orientation]++;
        confirmations[orientation == 0 ? 1 : 0] = 0;
        return confirmations[orientation] > SENSOR_CONFIRMATION_THRESHOLD;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
};
gouravd commented 9 years ago

This is how my fixAspectRatio() in CameraEncoder.java looks like mFullScreen.fixAspectRatio calls AspectRatio of fullframerect.java and mDisplayRenderer.fixAspectRatio calls fixAspectRatio of CameraSurfaceRenderer.java

public void fixAspectRatio(FullFrameRect.SCREEN_ROTATION orientation) { if (mFullScreen != null) mFullScreen.fixAspectRatio(orientation, true, isWideScreen()); mDisplayRenderer.fixAspectRatio(orientation); }

private boolean isWideScreen() { if(mCamera == null) return true; return mCamera.getParameters().getPreviewSize().height * 16 == mCamera.getParameters().getPreviewSize().width * 9; }

gouravd commented 9 years ago

This is how my fixAspectRatio in CameraSurfaceRenderer.java looks like

public void fixAspectRatio(FullFrameRect.SCREEN_ROTATION isVertical) { if (mFullScreenCamera != null) mFullScreenCamera.fixAspectRatio(isVertical, false, isWideScreen(mCameraEncoder)); }

gouravd commented 9 years ago

This is how my GLCameraView looks like

public class GLCameraView extends GLSurfaceView { private static final String TAG = "GLCameraView";

private static final double ASPECT_RATIO = 4.0 / 4.0;

protected ScaleGestureDetector mScaleGestureDetector;
private Camera mCamera;
private int mMaxZoom;

public GLCameraView(Context context) {
    super(context);
    init(context);
}

public GLCameraView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

private void init(Context context){
    mMaxZoom = 0;

}

public void setCamera(Camera camera){
    mCamera = camera;
    mCamera.setDisplayOrientation(90);
    Camera.Parameters camParams = mCamera.getParameters();
    if(camParams.isZoomSupported()){
        mMaxZoom = camParams.getMaxZoom();
        mScaleGestureDetector = new ScaleGestureDetector(getContext(), mScaleListener);
    }
}

public void releaseCamera(){
    mCamera = null;
    mScaleGestureDetector = null;
}

private ScaleGestureDetector.SimpleOnScaleGestureListener mScaleListener = new ScaleGestureDetector.SimpleOnScaleGestureListener(){

    int mZoomWhenScaleBegan = 0;
    int mCurrentZoom = 0;

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if(mCamera != null){
            Camera.Parameters params = mCamera.getParameters();
            mCurrentZoom = (int) (mZoomWhenScaleBegan + (mMaxZoom * (detector.getScaleFactor() - 1)));
            mCurrentZoom = Math.min(mCurrentZoom, mMaxZoom);
            mCurrentZoom = Math.max(0, mCurrentZoom);
            params.setZoom(mCurrentZoom);
            mCamera.setParameters(params);
        }

        return false;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        mZoomWhenScaleBegan =  mCamera.getParameters().getZoom();
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
    }
};

@Override
public boolean onTouchEvent(MotionEvent ev) {
    if(mScaleGestureDetector != null){
        if(!mScaleGestureDetector.onTouchEvent(ev)){
            // No scale gesture detected

        }
    }
    return true;
}

/**
 * Measure the view and its content to determine the measured width and the
 * measured height
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);

    if (width > height * ASPECT_RATIO) {
        width = (int) (height * ASPECT_RATIO + 0.5);
    }
    else {
        height = (int) (width / ASPECT_RATIO + 0.5);
    }

    setMeasuredDimension(width, height);
}

/*@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = View.MeasureSpec.getSize( widthMeasureSpec );
    int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);

    int height = (width * 9)/16;
    int newheightMeasureSpec = View.MeasureSpec.makeMeasureSpec( height, heightMode  );

    super.onMeasure(widthMeasureSpec, newheightMeasureSpec);
}*/

}