git clone –recursive https://github.com/huang8604/huang8604.github.io.git
录屏传输
录屏方法
录屏原理
1 创建虚拟屏幕,把内容绘制到屏幕绑定的 surface 中
针对第三方的应用,由于缺少权限。
需要通过 MediaProjection 的方式创建
1
2
3
4
5
6
7
8
projectionManager = ( MediaProjectionManager ) getSystemService ( MEDIA_PROJECTION_SERVICE );
meidaProjection = projectionManager . getMediaProjection ( resultCode , data );
//创建前台通知
//mediac 创建的surface
//创建虚拟屏幕
virtualDisplay = mediaProjection . createVirtualDisplay ( "name" , width , height , dpi , flag .. mirro , surface , null , null );
对于系统应用,可以直接创建虚拟屏幕
1
virtualDisplay = (( DisplayManager ) getsystemservice ( DISPLAY_SERVIcE )). createvirtualDisplay ( "Mainscreen" , width , height , dpi , surface , flgs : 1 < 10 | DisPlayManager . VIRTUAL_DISPLAY_FLAG PRESENTATION | DisPlayManage r . VIRTUAL_DISPLAY_FLKG_PUBLIC | DisPlayManager . VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
MediaFormat format = createFormate (... bit , fps ,);
MediaCodec codec = createMediaCodec ();
SurfactControl . createDisplay ();
configure ( codec , format ); //codec.configure(format)
Surface surface = codec . createInputSurface ();
setDisplaySurface ( display , surface ,...)
codec . start ();
try {
//开启循环
alive = encode ( codec , fd );
code . stop ();
} finally {
destoryDisplay ( diaplay );
codec . release ();
surface . release ();
}
private boolean encode ( MediaCodec codec , FileDescriptor fd ) throws IOException {
boolean eof = false ;
MediaCodec . BufferInfo bufferInfo = new MediaCodec . BufferInfo ();
while ( ! consumeRotationChange () && ! eof ) {
int outputBufferId = codec . dequeueOutputBuffer ( bufferInfo , - 1 );
eof = ( bufferInfo . flags & MediaCodec . BUFFER_FLAG_END_OF_STREAM ) != 0 ;
try {
if ( consumeRotationChange ()) {
// must restart encoding with new size
break ;
}
if ( outputBufferId >= 0 ) {
ByteBuffer codecBuffer = codec . getOutputBuffer ( outputBufferId );
if ( sendFrameMeta ) { //写入文件头
writeFrameMeta ( fd , bufferInfo , codecBuffer . remaining ());
}
//写入帧数据
IO . writeFully ( fd , codecBuffer );
}
} finally {
if ( outputBufferId >= 0 ) {
codec . releaseOutputBuffer ( outputBufferId , false );
}
}
}
return ! eof ;
}
private static void setDisplaySurface ( IBinder display , Surface surface , int orientation , Rect deviceRect , Rect displayRect , int layerStack ) {
SurfaceControl . openTransaction ();
try {
SurfaceControl . setDisplaySurface ( display , surface );
SurfaceControl . setDisplayProjection ( display , orientation , deviceRect , displayRect );
SurfaceControl . setDisplayLayerStack ( display , layerStack );
} finally {
SurfaceControl . closeTransaction ();
}
}
private static MediaCodec ( Codec codec , String encoderName ) throws IOException , ConfigurationException {
if ( encoderName != null ) {
Ln . d ( "Creating encoder by name: '" + encoderName + "'" );
try {
return MediaCodec . createByCodecName ( encoderName );
} catch ( IllegalArgumentException e ) {
Ln . e ( "Video encoder '" + encoderName + "' for " + codec . getName () + " not found\n" + LogUtils . buildVideoEncoderListMessage ());
throw new ConfigurationException ( "Unknown encoder: " + encoderName );
} catch ( IOException e ) {
Ln . e ( "Could not create video encoder '" + encoderName + "' for " + codec . getName () + "\n" + LogUtils . buildVideoEncoderListMessage ());
throw e ;
}
}
try {
MediaCodec mediaCodec = MediaCodec . createEncoderByType ( codec . getMimeType ());
Ln . d ( "Using video encoder: '" + mediaCodec . getName () + "'" );
return mediaCodec ;
} catch ( IOException | IllegalArgumentException e ) {
Ln . e ( "Could not create default video encoder for " + codec . getName () + "\n" + LogUtils . buildVideoEncoderListMessage ());
throw e ;
}
}
录屏保存为文件
1
2
3
4
5
6
7
8
9
10
11
String rootDir = Applistion . getInstance (). getExternalCacheDir () + "..." ;
File file = new File ( path + "test.h264" );
FileOutputStream fileOutputStream = null ;
fileOutputStream = new FileOutputStream ( file );
fileOutputStream . write ( data );
fileOutstream . flush ();
fileOutStream . close ();
fileOutStream = null ;
file = null ;
录屏数据编码传输
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ServerSocket mServer = null ;
Socket mSocket = null ;
void init () {
new Thread ( new Runnable (){
@Override
public void run (){
while ( true ){
try {
if ( mServer == null ){
//新建服务socket 服务
mServer = new ServerSocker ( 6666 );
mSocket = mServer . accept ();
//建立连接后写入数据
mSocket . getOutputStream (). write ( 66 );
}
} catch ( Exception e ){
e . pri ...
}
}
}
}). start ();
public Socket getSocket (){
return mSocket ;
}
}
ConnectionManager . getInstance (). getSocket (). getOutPutStream (). write ( data );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
MediaCodeec . BufferInfo buffInfo = new MediaCode . BufferInfo ();
while ( running && ! eof ){
int outputBufferId = codec . dequeueOutputBuffer ( bufferInfo , timeouts - 1 );
eof = ( bufferInfo . flags & MediaCodec . BUFFER_FLAG_END_OF_STREAM ) != 0 ;
try {
if ( outBufferId >= 0 ){
ByteBuffer codeBuffer = codec . getoutputBuffer ( outputBufferId );
byte [] data = new byte [ codecBuffer . remaining () ] ;
codecBuffer . get ( data );
int h264Len = data . length ;
ByteBuffer dataBuffer = ByteBuffer . allocate ( 4 );
dataBuffer . putInt ( h264Len );
dataBuffer . flip ();
byte [] dataLen = new byte ( 4 );
dataLenBuf . get ( dataLen );
ConnectionManager . getInstance (). getSocket (). getOutputStream (). write ( dataLen );
ConnectionManager . getInstance (). getSocket (). getOutputStream (). write ( data );
}
} finally {
if ( outBufferId >= 0 ){
codec . releaseOutputBuffer ( outBufferId , false );
}
}
}
客户端的接受
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
Socket mClientSocket = null ;
new Thread ( new Runnable (){
@Overrride
public void run (){
try {
mClientSocket = new Socket ( ip , port 6666 );
byte [] data = new byte [ 1 ] ;
int len = mClientSocket . getInputStream . read ( data );
//接受一个头字节
if ( len == 1 && data [ 0 ] == 66 ){
//ok
runOnUiThread ( new Runnable (){
@Override
public void run (){
//...
}
});
}
while ( running ){
byte [] headLen = new byte [ 4 ] ;
int readLen = 0 ;
while ( readLen < headLen . length ){
readLen = readLen + mClientSocket . getInputStream . read ( headLen , readLen , headLen . length - readLen );
}
//读取的数组 转为 长度
int h264Len = byteArrayToInt ( headLen );
byte [] rawData = new byte [ h264Len ] ;
int rawDataLen = 0 ;
while ( rawDataLen < h264Len ){
rawDataLen = rawDataLen + mClientSocket . getInputStream . read ( rawData , rawDataLen , h264Len - rawDataLen );
}
//rawData 就是每一帧的图像数据
//生产放入数组
synchronized ( mH264List ){
mH264List . add ( rawData );
//消费者可能在等待
mH264List . notify ();
}
//保存文件
// fileOutputStream.write(h264Data,0,readLen1);
}
} catch ( Exception e ){
e . pritStack
}
}
}). start ();
public static int byteArrayToInt ( byte [] bytes ) {
// 确保输入的byte数组长度为4
if ( bytes . length != 4 ) {
throw new IllegalArgumentException ( "Byte array must be 4 bytes long" );
}
return ( bytes [ 0 ] & 0xFF ) << 24 |
( bytes [ 1 ] & 0xFF ) << 16 |
( bytes [ 2 ] & 0xFF ) << 8 |
( bytes [ 3 ] & 0xFF );
}
public static int byteArrayToInt ( byte [] bytes ) {
// 确保输入的byte数组长度为4
if ( bytes . length != 4 ) {
throw new IllegalArgumentException ( "Byte array must be 4 bytes long" );
}
// 使用 ByteBuffer 将 byte[] 转换为 int
ByteBuffer buffer = ByteBuffer . wrap ( bytes );
return buffer . getInt ();
}
public static int byteToInt2 ( byte [] src , int offset ){
return value ;
}
录屏数据解码显示
client 先从 codec 请求 空的 input buffer,填满
送给 codec,codec 内部再解码
显示端从 codec 取出 outputbuffer ,opengl 绘制,或者调用接口处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
MediaCodec mediaCodec = null ;
void inintMediaCode (){
try {
mediaCodec = MediaCodec . createDecoderByType ( MediaFormat . MIMETYE_VIDEO_AVC );
MediaFormat format = MediaFormat . createVideoFormat ( MediaFormat . MIMETYPE_VIDEO_AVC );
mediaCodec . configure ( format , surface , null , 0 );
mediaCodec . start ();
} catch ( Exception e ){
e . print ...
}
}
ArrayList < byte []> mH264List = new ArrayList ();
void decodeH264 (){
new Thread ( new Runnable (){
@Override
public void run (){
if ( mediacode != null ){
while ( running ){
//消费
byte [] rawH264 = null ;
synchronized ( mH264List ){
if ( mH264List , size () > 0 ){
rawH264 = mH264List . get ( 0 );
mH264List . remove ( 0 );
}
if ( rawH264 == null ){
try {
//wait的时候,锁回释放掉
mH264List . wait ();
} catch (){
}
continue ;
}
}
//解码
ByteBuffer [] intputBuffer = mediaCode . getInputBuffers ();
int inputIndex = - 1 ;
while ( inputIndex < 0 ){
inputIndex = mediaCode . deququeInputBuffer ( timeouts 10000 );
}
ByteBuffer bytebuffer = inputBuffers [ inputIndex ] ;
bytebuffer . clear ();
bytebuffer . put ( rawH264 );
mediaCode . queueInputBuffer ( inputIndex , 0 , rawH264 . lenth , 0 , 0 );
//渲染到surface
MediaCodec . BufferInfo info = new MediaCodec . BufferInfo ();
int outputIndex = mediaCode . dequeueOutputBuffer ( info , timeout 10000 );
if ( outputIndex >= 0 ){
mediaCode . releaseOutputBuffer ( outputIndex , true ); //render = true ,就会渲染
}
}
}
}
}). start ();
}
//创建一个SurfaceView
SurfaceView surfaceView = findViewById (...);
SurfaceView . getHolder (). addCallback ( new SurfaceHolder . callback (){
public void surfaceChanged ( holder , format , width , height )
initMeidaCodec ( holder . getSurface (), w , h );
});
投屏相关
创建虚拟屏幕有一些FLAG
同显模式 mirror
异显模式 OWN_CONTENT
1
virtualDisplay = (( DisplayManager ) getsystemservice ( DISPLAY_SERVIcE )). createvirtualDisplay ( "Mainscreen" , width , height , dpi , surfaceHags : 1 < 10 | DisPlayManager . VIRTUAL_DISPLAY_FLAG PRESENTATION | DisPlayManager . VIRTUAL_DISPLAY_FLKG_PUBLIC | DisPlayManager . VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY );
surfaceHags: 1 < 10 其实为VIRTUAL_DISPLAY_FLAG_TRUSTED flag
VIRTUAL_DISPLAY_FLAG_SECURE ,没有该flag,截图或者支付等界面是不允许打开在该屏幕
不同尺寸的投屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//服务端
public class Config {
public static int sWidth = 1280 ;
public static int sHeight = 720 ;
public static int sDpi = 213 ;
}
createvirtualDisplay 有相应的参数 , 宽 高
//客户端
获取屏幕宽高
DisplayMetries metries = getResource (). getDisplayMetrics ();
int widthDiplay = metrics . widthPixels ;
int heightDiplay = metrics . heightPixels ;
ViewGroup . LayoutParams layoutParams = surfaceView . getLayoutParams ();
layoutParams . width = w ;
layoutParams . height = h ;
surfaceView . setLayoutParams ( layoutParams );
runOnUiThread ( new Runnable (){
@Overrride
public void run (){
resize ();
}
});
异屏幕启动方式
一 Dialog
二
三 Activity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void startActivityOtherDisplay(Context context){
DisplayManager mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
Display[] displays = mDisplayManger.getDisplays();
if(display.length > 1){
Intent intent = new Intent();
intent.setClass(context,MainActivity.class);
AcitivityOptions options = ActivityOptions.makeBasic();
//设置最后一个屏幕的id,这里可以根据name等特征值设置ID
options.setLaunchDisplayId(displays[displays.length -1].getDisplayId());
context.startActivity(intent,options.toBunndle());
}else{
Log.d("test","only one display");
}
}
触摸部分
触摸事件注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MotionEvent event = MotionEvent . obtain ( now , now , action , pointCount , coords , metaState 0 , buttonState 0 , xPrecision 1 , yPresision 1 , deviceID : 0 , edgeFlags 0 , InputDevice . SOURCE_TOUCHSCREEN , flags 0 )
//hide 方法,apk需要通过反射调用
MotionEvent . setDisplayId ( 1 );
//反射方法 begin
public static void setDisplayId ( MotionEvent event , int displayId ){
try {
Mothed method = MotionEvent . class . getMethod ( "setDisplayId" , int . class );
method . invoke ( event , displayId );
} catch (){}
}
//反射方法 end
Instrumentation instrumentation = new Instrumentation ();
instrumentaion . sendPointerSync ( MotionEvent )
音频部分
服务端
AudiRecord 版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int AUDIO_SOURCE = MediaRecorder . AudioSOURCE . REMOTE_SUBMIX ;
publicint CHANNEL = AUdiOFORMAT . CHANNEL_IN_STEREO ;
static finalint AUDIO_FORMAT = AUdioFOrmat . ENCODING_PCM_16BIT ;
public static finalint SAMPLE RATE = 44100 ;
private static AudioDataManager sAudioDataManager = new AudioDataManager ();
boolean mRecording = true ;
AudioRecord audioRecord = null ;
int buffersize = 0 ;
void startRecord ( final Socket socket ){
mRecording = true ;
buffersize = AudioRecord . getMinBUfferSiZe ( SAMPLE RATE , CHANNEL , AUDIO FORMAT );
audioRecord = new AudioRecord ( AUDIO SOURCE , SAMPLE RATE , CHANNEL , AUDIO_FORMAT , buffersize );
audioRecord . startRecording ();
new Thread ( new Runnable (){
@0verride
public void run (){
while ( mRecording ){
bytel ] buffer = new byte [ buffersize ] ;
int len = audioRecord . read ( buffer , offsetinBytes : o , buffer . length );
if ( len > 0 ){
try {
if ( socket != null ){
socket . getoutputstream (). write ( buffer , off : 0 , len );
} catch ( Exception e ){
e . printstackTrace ();
}
}). start ();
优化版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
String ADDRESS = "audio.send" ;
LocalserverSocket mLocalServerSocket = null ;
Localsocket msocket = null ;
void startRecordsocket ( Socket socket ){
testplay ();
mRecording = true ;
bufferSize = AudioRecord . getMinBUfferSiZe ( SAMPLE RATE , CHANNEL , AUDIO FORMAT );
audioRecord = NeW AUdIOReCOrd ( AUDIO SOURCE , SAMPLE RATE , CHANNEL , AUDIO FORMAT , buffersize ); audioRecord . startRecording ();
new Thread ((( Runnable )() → {
try {
if ( mLocalserverSocket != null ){
mLocalserverSocket . close ();
mLocalserverSocket = null ;
}
if ( mSocket != null ) mSocket . close ();
mLocalserverSocket = new LocalServerSocket ( ADDRESS );
mSocket = mLocalserverSocket . accept ();
//组塞式读取数据
while ( mRecording ){
byte [] data = new byte [ 1024 * 4 ] ;
int len = 0 ;
while ( len < data . length ){
//获取数据
len = len + mSocket . getInputstream (). read ( data , len , len : data . length len );
if ( len > 0 ){
if ( socket != null ){
//将获取的数据发送到客户端
socket . getOutputStream (). write ( data , off : 0 , len )
}
}
}
} catch ( Exception e ){
e . printstackTrace ();
). start ();
}
av/services/audioflinger/THreads.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
int connect_fd = - 1 ;
# define ADDRESS "audio.send"
sizz_t AudioFLinger :: PlaybackThread :: threadLoop_write ()
sszie_t frameWritten = mNormalSink - write ...
//add begin
//include <sys/socket.h>
//include <sys/un.h>
//需要区分 REMOTE_SUBMIX
// isSingleDeviceTyep(outDevices,AUDIO_DEVICE_OUT_REMOTE_SUBMIX)
if ( connect_fd <= 0 ){
connect_fd = SOCket ( AF UNIX , SOCK STREAM , 0 );
if ( connect fd < 0 ){
ALoGI ( "init socket fail" );
return 1 ;
}
sockaddr_un srv_addr {};
memset ( & srv addr , 0 , sizeof ( srv addr ));
srv_ddr . sun_path [ 0 ]= '\0' ; //注意
strcpy ( srv_addr . sun_path + 1 , ADDRESS );
srv_addr . sun_family = AF_UNIX ;
int nameLen = strlen ( ADDRESS );
int len = 1 + nameLen + offsetof ( struct sockaddr_un , sun_path );
int ret = connect ( connect_fd ,( struct sockaddr * ) & srv addr , len );
if ( ret == - 1 ){
ALOGI ( "connect socket fail:%d" , ret );
connect fd =- 1 ;
} else {
AL0GI ( "connect success,start thread" );
}
} else {
if ( framesWritten > 0 ){
int result = :: send ( connect_fd ,( char * ) mSinkBuffer + offset , framesWriiten * mFrameSize , 0 );
if ( result < 0 ){
:: close ( connect_fd );
connect_fd = - 1 ;
ALOGI ( "debug this " );
}
}
}
//add end
if ( frameWritten > 0 )
...
客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void receiveData ( final socket socket ){
if ( socket != null ){
final byte [] buf = new byte [ buffersize ] ;
new Thread ( new Runnable (){
@override
public void run (){
while ( mRunning ){
try {
int len = socket . getInputstream (). read ( buf );
playAudio ( buf , len );
} catch ( Exception e ){
e . printstackTrace ();
}
}). start ();
void playAudio ( byte [] data , int len ){
if ( mAudioTrack != null ){
mAudioTrack . play ();
mAudioTrack . writ ( data , offset : 0 , len )
}
}
ByteBuffer 介绍
功能再优化
视频延时测试验证
悬浮窗口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
SytemTimeDialog
SystemTimeDialog ( Contextcontext ){
mContext = context ; // 取得系统窗体
mWindowManager = ( WindowManager ) context . getSystemservice ( ConteXt . WINDOW SERVICE );
// 窗体的布局样式
mLayoutParams = new WindowManager . LayoutParams ();
mLayoutParams . setTitle ( "TimeShow" );
//设置窗体显示类型-TYPE APPLICATION OVERLAY
mLayoutparams . type = Windowanager . LayoutParamS . TYPE_APPLICATION_OVERLAY ; //2018; //windowManager.LayoutParams.TYPE POINTER;mLayoutParams.flags = windowanager.LayoutParamS.FLAG NOT TOUCH MODAL| WindowManager.LayoutParaMS.FLAG NOT FOCUSABLE;mLayoutParams.width = WindowManager.LayoutParamS.WRAP CONTENT;
mLayoutParams . height = WindowManager . LayoutParamS . WRAP_CONTENT ;
mLayoutParams . gravity = Gravity . LEFT | Gravity . TOP ;
//mLayoutParams.privateFlags = mLayoutParams.priVateFLagS | PRIVATE FLAG SYSTEM APPLICATION OVERLAY;
timeTextView = new TextView ( mContext );
timeTextView . setTextColor ( Color . RED );
timeTextView . setTextsize ( 20 );
timeTextView . setText ( "HelloWorld" )
timeTextView . setOnTouchListener ( onTouchDrawListener )
//更新拖拽
mWindowManager . updateViewLayout ( v . mLayoutParams );
//更新显示毫秒的悬浮窗
void show (){
ValueAnimator animator = ValueAnimator . ofInt ( 0 , 100 );
animator . addUpdateListener ( new ValueAnimator . AnimatorUpdateListener (){
@0verride
public void onAnimationUpdate ( ValueAnimator animation ){
long time = System . currentTimeMillis ();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat ( pattern : "HH:mm:ss :SSS" );
String showTime = simpleDateFormat . format ( time );
timeTextView . setText ( showTime );
timeTextView . setBackgroundcolor ( Color . BLACK ) |
});
animator . setDuration ( 2000 );
animator . setRepeatCount ( - 1 );
animator . start ();
mWindowManager . addView ( timeTextView , mLayoutParams );
}
音频延迟测试方案
source 端和 sink端 同时发声,再录音,分析波峰的间隔时间。
内录的方案改造成播放声音。
audiopolicy/enginedefault/src/Engine.cpp
选择合适的音源播放,比如拨号。声音大小要有区分
录制投屏的声音文件。
通过 audacity软件 进行mp3波形分析
竞品的数据如何采集
adb shell dumpsys meida_audio_flinger > 1.txt
查看 standby 为 no,查看发声output devices
优化方案
视频方面
尽量多线程,数据生产,解码,渲染单独运行
多线程 生产者的数据集合需要进行统计集合大小。在卡顿的时候,可以方便定位。
a. 更加体验和硬件性能选择 码率和分辨率。
b. 根据生产者消费者模型,定位挤压数据位置。 针对瓶颈考虑丢帧。比如网络卡顿,突然来了很多帧,可以考虑跳过数据
c. 如果是软解码,如使用ffmpeg等,可以考虑多线程解码
音频方面
AudioRecord 自带100ms延时
在 AudioTrack 中拦截,直接发送出去
优点:速度快
缺点 : AudioTrack 有多数据源的写入。数据源参数采样率声道等都不一样
复杂度比较高
在AudioFlinger 中进行拦截,不走 AudioRecord
还需要保持REMOTE_SUB_MIX,让OutputThread 合成一个音轨。
强制桌面模式
可以强制异显屏幕显示出桌面
-–end–