begeekmyfriend/yasea

请问怎么用这个实现屏幕推流

FrankLove opened this issue · 26 comments

你好,请问如果我想把这个改成手机屏幕画面推流,不用摄像头,该怎么处理呢?

@begeekmyfriend 博主,google的这个demo我之前有看过,他只是通过MediaProjection然后显示画面到surface,这个流程我知道,但是这个跟屏幕推流还是不同的。我之前根据网上的demo做了一个硬解码后直接用TCP发送h264裸流的,PC端然后通过SDL渲染播放,现在我是想把硬解码拿到的h624数据用你这个推流出去,请教博主这个能不能提供一下思路?

你的需求有人提过的,你可以从question label的issue里去找,说白了就是SrsEncoder.javaFlvMuxer的数据输入接口,你也可以自己加断点调试

好的,谢谢博主指点~

@begeekmyfriend 博主,不好意思,再请教一下,我修改了数据的输入源,
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void initEncoder(MediaProjection mediaProjection) throws IOException
{

    MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);
    format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
    format.setInteger(KEY_MAX_INPUT_SIZE,0);
    format.setInteger(KEY_BIT_RATE, 4200000);//width * height 200000
    format.setInteger(KEY_FRAME_RATE, 20);
    format.setInteger(KEY_I_FRAME_INTERVAL, 3);

    mVEndcoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
    mVEndcoder.setCallback(new MediaCodec.Callback() {
        @Override
        public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, int i) {

        }

        @Override
        public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec, int outputBufferId, @NonNull MediaCodec.BufferInfo bufferInfo) {
            if (outputBufferId >= 0)
            {
                ByteBuffer localByteBuffer = mediaCodec.getOutputBuffer(outputBufferId);
                mEncoder.onEncodedAnnexbFrame(localByteBuffer,bufferInfo);
                localByteBuffer.flip();
                mediaCodec.releaseOutputBuffer(outputBufferId, false);
            }
        }

        @Override
        public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
        }

        @Override
        public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {

        }
    });
    mVEndcoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    Surface surface = mVEndcoder.createInputSurface();
    mVirtualDisplay = mediaProjection.createVirtualDisplay("-display", mWidth, mHeight, 1,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null);
    mVEndcoder.start();
}

直接用了SrsEncoder.java 的这个方法
public void onEncodedAnnexbFrame(ByteBuffer es, MediaCodec.BufferInfo bi) {
mp4Muxer.writeSampleData(videoMp4Track, es.duplicate(), bi);
flvMuxer.writeSampleData(videoFlvTrack, es, bi);
}
但是却没有推送视频数据,请问这可能是什么原因呢
这是我之前发送H264裸流的组包,是不是数据封装哪里没对上
private byte[] sps_pps_buf;
private void onEncodedAvcFrame(ByteBuffer bb, final MediaCodec.BufferInfo vBufferInfo)
{
int offset = 4;

    if (bb.get(2) == 0x01)
    {
        offset = 3;
    }
    int type = bb.get(offset) & 0x1f;
    if (type == NAL_SPS) {

        sps_pps_buf = new byte[vBufferInfo.size];
        bb.get(sps_pps_buf);
        //TODO
    } else if (type == NAL_SLICE  ) {
        final byte[] bytes = new byte[vBufferInfo.size];
        bb.get(bytes);
        if (null != screenCaputreListener) {
            screenCaputreListener.onImageData(bytes);
        }

    } else  if (type == NAL_SLICE_IDR)
    {
        // I帧,前面添加sps和pps
        final byte[] bytes = new byte[vBufferInfo.size];
        bb.get(bytes);

        byte[] newBuf = new byte[sps_pps_buf.length + bytes.length];
        System.arraycopy(sps_pps_buf, 0, newBuf, 0, sps_pps_buf.length);
        System.arraycopy(bytes, 0, newBuf, sps_pps_buf.length, bytes.length);
        if (null != screenCaputreListener) {
            screenCaputreListener.onImageData(newBuf);
        }

    }
}

你可以用Android Studio的断点调试功能

@begeekmyfriend 好的,现在已经可以了。博主,给你提个建议,数据发送这块不建议用java的原生socket,改成jni调用C语言的tcp效率会大幅提高

好的,谢谢建议!

@begeekmyfriend 博主,还想请教下,目前测试过程中推流到服务器,然后播放端画面最低也有500ms的延迟,改成屏幕推流后数据量相对比摄像头大了不少,内网ping 服务器延迟<1ms 的情况下,播放还会间歇性的卡顿,这个有什么办法优化下么?。我之前传输h264裸流的情况下,java socket大概1S延迟,改成C后最低到100-200ms,但是这个推流我改成了C 的TCP传输,居然没有什么改善,实在是奇怪

调整一下码流,相同数据量传输层应该是一样的延时

好的,博主你什么时候回考虑下加入屏幕推流这块呢,我发现我改的这个屏幕推流,相同的码流设置,在手机上推还勉强能看,在平板上简直卡到不行,码流现在调的也不高,看着画面都还有些模糊,每秒的数量一般也只有200-500kb。还有我发现一个问题
public void writeVideoSample(final ByteBuffer bb, MediaCodec.BufferInfo bi) {
if (bi.size < 4) return;
int offset = 4;
long pts = (bi.presentationTimeUs / 1000); //这个值如果定义为整型在手机上正常,在我测试的一个平板上出来的值是负数,因为这个值bi.presentationTimeUs超过了整型最大长度,不知道为什么会这样?导致无法正常推流,可能要改成long比较安全点

问题是我不玩平板的,没设备,你可以用SRS和ffplay去看一下,是否网络的问题。
另外PTS本来就是long类型,至于平板为啥溢出,也许System.nanoTime()不一样,可以打印这一行查看一下

好的,博主,还想请问下这个是我用ffplay播放,别的推流工具ffplay打印的一个参数
Stream #0:2: Video: h264 (Constrained Baseline), yuv420p(progressive), 1422x720, 25 fps, 25 tbr, 1k tbn, 50 tbc
下面这是用你的推流ffplay打印的参数
Stream #0:2: Video: h264 (Baseline), yuv420p(tv, unknown/bt470bg/unknown, progressive), 1080x674, 1k tbr, 1k tbn
第一个延迟1S左右,第二个延迟大概10s左右(如果换成专业的直播播放器,两者延迟就都差不多0.5-1S[不过你这个推流会出现间歇性的突然卡一下,频率比较高,不知道为啥] ,但是用ffplay播放差距却这么大,实在很奇怪),我发现这两个有好几处数据不同,根据这个两个流信息对比,能看出有啥问题吗?

没有你的环境我也不好说啊,从编码格式上看都是一样的,也许yasea没有用jni去写socket的缘故?我不清楚。你所谓“数据不同”指的是啥,yasea本来就是为移动端去定制的,你可以用手机对比一下效果,平板我没有做过测试。

嗯嗯,我现在都是在手机上测试,目前唯一的问题就是,正常情况下0.5S的延迟,还好,但是推流的画面经常突然就会卡一下,比如我在手机住界面来回滑动,就很容出现从1-2这个页面过渡的时候不是平滑过渡,而是突然卡一下然后就跳到2这个页面,这种情况可能是什么原因引起的呢?

关于延时的问题,不知这里是否可供参考?#785

krmao commented

@FrankLove 兄弟,你这个屏幕推送有开源代码参考吗,重复轮子也不好造啊

@krmao 你参考我上面的代码,然后稍微修改下这个开源库,注释掉摄像头那部分代码就可以了。还可以考虑使用其他两种方式librtmp和ffmepg实现

krmao commented

@FrankLove 感谢回复
由于对这块不是很熟, 一时无从下手
不过刚刚我已经成功运行了一个案例, 只是推流技术不一样 https://github.com/eterrao/ScreenRecorder
不知后面阁下是否继续研究在不同手机间远程控制

@krmao 不同手机间远程控制没研究过这方面,https://github.com/eterrao/ScreenRecorder 这个demo测试了下感觉已经实现的很不错了,用这个做屏幕推流应该没啥问题了

如何改成 c socket 发送 推流,优化延迟

@begeekmyfriend 好的,现在已经可以了。博主,给你提个建议,数据发送这块不建议用java的原生socket,改成jni调用C语言的tcp效率会大幅提高

你是使用c语言的 simplermtp https://github.com/faucamp/SimpleRtmp/tree/master/src/com/github/faucamp/simplertmp/io
实现的吗?方便 透露一下怎么实现的,我使用jni 替换 DefaultRtmpPublisher 实现,一直 报错

@xieyilong 屏幕推流你直接用这个库就可以了,librtmp实现的,底层是C语言的socket https://github.com/eterrao/ScreenRecorder 当前这个库其实延迟也是可以接受的,我改成C语言发送对延迟也没有什么改善,目前博主这个库的延迟跟专业的sdk在内网下都是一样的0.5s左右,但是偶尔出现跳帧不流畅的原因我怀疑是因为他没有把数据放到队列里面,直接通过回调来发送,在某些情况下可能会出现数据量过大而来不及发送

插一句,队列大小是可调

@begeekmyfriend 好的,现在已经可以了。博主,给你提个建议,数据发送这块不建议用java的原生socket,改成jni调用C语言的tcp效率会大幅提高
@FrankLove
请问下,为什么不推视频流呢,需要改哪里?