Android系统音视频架构

在Android平台通过V4L2接口获取video流 本文介绍了通过v4l2接口获取video数据的主要步骤。在android平台camera hal层调用v4l2接口实现video功能。根据android camera hal接口逻辑把v4l2接口的调用分为如下步骤。四大主要步骤:枚举码流格式,分辨率,帧率信息配置流参数和初始化buffer启动流并获取数据关闭流和反初始化buffer。 阅读详情

本博客转载于:http://leeeyou.xyz/2017/04/15/blog-2017-04-15-Android系统音视频架构/



1、Android系统上的音频框架

一个好的系统架构需要尽可能地降低上层与具体硬件的耦合,这既是涉及操作系统的目的,对于音频系统也是如此。音频系统的雏形框架可以简单的用下图来表示:

在这个图中,除去Linux本身的Audio驱动外,整个Android音频实现都被看成了User。因而我们可以认为Audio Driver就是上层与硬件间的“隔离板”。但是如果单纯采用上图所示的框架来设计音频系统,对上层应用使用音频功能是不小的负担,显然Android开发团队还会根据自身的实际情况来进一步细化“User”部分。

细化的根据还是Android的几个层次结构,包括应用层、framework层、库层以及HAL层,如下图所示:

Framework

相信大家可以马上想到MediaPlayer和MediaRecorder,因为这是我们在开发音频相关产品时使用最广泛的两个类。实际上,Android也提供了另两个相似功能的类,即AudioTrack和AudioRecorder,MediaPlayerService内部的实现就是通过它们来完成的,只不过MediaPlayer/MediaRecorder提供了更强大的控制功能,相比前者也更易于使用。我们后面还会有详细介绍。

除此以外,Android系统还为我们控制音频系统提供了AudioManager、AudioService及AudioSystem类。这些都是framework为便利上层应用开发所设计的。

Libraries

我们知道,framework层的很多类,实际上只是应用程序使用Android库文件的“中介”而已。因为上层应用采用java语言编写,它们需要最直接的java接口的支持,这就是framework层存在的意义之一。而作为“中介”,它们并不会真正去实现具体的功能,或者只实现其中的一部分功能,而把主要重心放在库中来完成。比如上面的AudioTrack、AudioRecorder、MediaPlayer和MediaRecorder等等在库中都能找到相对应的类。这一部分代码集中放置在工程的frameworks/av/media/libmedia中,多数是C++语言编写的。

除了上面的类库实现外,音频系统还需要一个“核心中控”,或者用Android中通用的实现来讲,需要一个系统服务(比如ServiceManager、LocationManagerService、ActivityManagerService等等),这就是AudioFlinger和AudioPolicyService。它们的代码放置在frameworks/av/services/audioflinger,生成的最主要的库叫做libaudioflinger。

音频体系中另一个重要的系统服务是MediaPlayerService,它的位置在frameworks/av/media/libmediaplayerservice。

因为涉及到的库和相关类是非常多的,建议大家在理解的时候分为两条线索。
其一,以库为线索。比如AudioPolicyService和AudioFlinger都是在libaudioflinger库中;而AudioTrack、AudioRecorder等一系列实现则在libmedia库中。

其二,以进程为线索。库并不代表一个进程,进程则依赖于库来运行。虽然有的类是在同一个库中实现的,但并不代表它们会在同一个进程中被调用。比如AudioFlinger和AudioPolicyService都驻留于名为mediaserver的系统进程中;而AudioTrack/AudioRecorder和MediaPlayer/MediaRecorder一样实际上只是应用进程的一部分,它们通过binder服务来与其它系统进程通信。

在分析源码的过程中,一定要紧抓这两条线索,才不至于觉得混乱。

HAL

从设计上来看,硬件抽象层是AudioFlinger直接访问的对象。这说明了两个问题,一方面AudioFlinger并不直接调用底层的驱动程序;另一方面,AudioFlinger上层(包括和它同一层的MediaPlayerService)的模块只需要与它进行交互就可以实现音频相关的功能了。因而我们可以认为AudioFlinger是Android音频系统中真正的“隔离板”,无论下面如何变化,上层的实现都可以保持兼容。

音频方面的硬件抽象层主要分为两部分,即AudioFlinger和AudioPolicyService。实际上后者并不是一个真实的设备,只是采用虚拟设备的方式来让厂商可以方便地定制出自己的策略。

抽象层的任务是将AudioFlinger/AudioPolicyService真正地与硬件设备关联起来,但又必须提供灵活的结构来应对变化——特别是对于Android这个更新相当频繁的系统。比如以前Android系统中的Audio系统依赖于ALSA-lib,但后期就变为了tinyalsa,这样的转变不应该对上层造成破坏。因而Audio HAL提供了统一的接口来定义它与AudioFlinger/AudioPolicyService之间的通信方式,这就是audio_hw_device、audio_stream_in及audio_stream_out等等存在的目的, 这些Struct数据类型内部大多只是函数指针的定义,是一些“壳”。当AudioFlinger/AudioPolicyService初始化时,它们会去寻找系统中最匹配的实现(这些实现驻留在以audio.primary., audio.a2dp. 为名的各种库中)来填充这些“壳”。

根据产品的不同,音频设备存在很大差异,在Android的音频架构中,这些问题都是由HAL层的audio.primary等等库来解决的,而不需要大规模地修改上层实现。换句话说,厂商在定制时的重点就是如何提供这部分库的高效实现了。

2、Android系统上的视频框架

multimedia framework 架构 由三大部分构成:供上层程序调用的java API,连接java和C/C++的jni部分,多媒体引擎(stagefright)和codec接口(openmax interface)。

我们可以发现上层APK要播放视频,首先得获得一个player,而这个player的类型根据你媒体文件的类型来决定的,分配的任务由mediaplayerservice来完成,除了获得player外最主要的是到底选用哪种编码器进行编解码,这个过程由awesomeplayer和omxcodec来完成,至于声音和图像就交由audioflinger和surfaceflinger来完成了。

android中播放视频的两种方式

通过VideoView配合MediaPlayerControl实现

概念介绍

VideoView是android系统提供的一个媒体播放显示和控制的控件。其结构层次如下:

     
1
     
VideoView extends SurfaceView implements MediaController.MediaPlayerControl

通过VideoView的原型可知:如果构建更为复杂和有特色个性的视频View,需要继承SurfaceView 和实现MediaPlayerControl接口。其中SurfaceView 为显示提供支持,MediaPlayerControl则为媒体控制提供了支持

基本方法介绍

VideoView:用于播放一段视频媒体,它继承了SurfaceView,位于”android.widget.VideoView”,是一个视频控件。既然是播放一段视频,那么不可避免的要涉及到一些开始、暂停、停止等操作,VideoView也为开发人员提供了对应的方法,这里简单介绍一些常用的:

     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
     
    int getCurrentPosition():获取当前播放的位置。
    int getDuration ():获取当前播放视频的总长度。
    isPlaying ():当前VideoView是否在播放视频。
    void pause ():暂停
    void seekTo (int msec):从第几毫秒开始播放。
    void resume ():重新播放。
    void setVideoPath (String path):以文件路径的方式设置VideoView播放的视频源。
    void setVideoURI (Uri uri):以Uri的方式设置VideoView播放的视频源,可以是网络Uri或本地Uri。
    void start ():开始播放。
    void stopPlayback ():停止播放。
    setMediaController (MediaController controller):设置MediaController控制器。
    setOnCompletionListener (MediaPlayer.onCompletionListener l):监听播放完成的事件。
    setOnErrorListener (MediaPlayer.OnErrorListener l):监听播放发生错误时候的事件。
    setOnPreparedListener (MediaPlayer.OnPreparedListener l)::监听视频装载完成的事件。

上面的一些方法通过方法名就可以了解用途。和MediaPlayer配合SurfaceView播放视频不同,VideoView播放之前无需编码装载视频,它会在start()开始播放的时候自动装载视频。并且VideoView在使用完之后,无需编码回收资源。

基本使用

要使用 VideoView类播放视频,首先要在布局文件中添加VideoView组件,然后在Activity中获取该组件,并使用 VideoView.setVideoPath()或VideoView.setVideoURI() 方法加载需要播放的视频,最后调用 start()方法播放视频。VideoView类还提供了stop()和pause()方法,用于停止或暂停视频播放。

代码示例
     
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
     
package com.example.leeyou.drr.di;
import java.io.File;
import android.app.Activity;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Bundle;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;
public class MainActivity extends Activity {
    private VideoView video;
    /**
     * Called when the activity is firstcreated.
     */
    @Override
    publicvoidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        video = (VideoView) findViewById(R.id.video);
        File file = new File( "/sdcard/1.mp4");
        MediaController mc = newMediaController(MainActivity. this);       // 创建一个MediaController对象
        if (file.exists()) {
            video.setVideoPath(file.getAbsolutePath());
            video.setMediaController(mc);       // 将VideoView与 MediaController关联起来
            video.requestFocus();       // 设置VideoView获取焦点
            try {
                video.start();      // 播放视频
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 设置 VideoView的Completion事件监听器
            video.setOnCompletionListener( new OnCompletionListener() {
                @Override
                public voidonCompletion(MediaPlayer mp) {
                    Toast.makeText(MainActivity. this, "视频播放完毕!", Toast.LENGTH_SHORT).show();
                }
            });
        } else {
            Toast.makeText ( this , "要播放的视频文件不存在 ", Toast.LENGTH_SHORT ).show();
        }
    }
}

通过MediaPlay配合SurfaceView实现

基本使用

1、在布局文件中插入SurfaceView组件,其语法格式如下:

     
1
2
3
4
5
6
     
<SurfaceView
         android:id=”@+id/ID号”
         android:background=”背景”
         android:keepScreenOn=” true| false”
         android:layout_width=”宽度”
         android:layout_height=”高度” />

2、创建MediaPlayer对象,并加载要播放的视频。加载视频的方法和上一篇文章中介绍的加载音频的方法一样,这里不再详述。

3、将视频画面输出到SurfaceView,语法格式如下:
MediaPlayer.setDisplay(SurfaceHolder sh)
参数sh用于指定SurfaceHolder对象,可以通过SurfaceView.getHolder()方法获得。SurfaceHolder可以理解为SurfaceView装载需要显示的一帧帧图像的容器

4、调用MediaPlayer的play()、stop()、pause()等方法控制视频播放。

代码示例
     
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
     
package com.example.leeyou.drr.di;
import java.io.IOException;
import android.app.Activity;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Bundle;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
    private MediaPlayer mp;
    private SurfaceView sv;
    @Override
    publicvoidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mp = new MediaPlayer();
        sv = (SurfaceView) findViewById(R.id.surfaceView1);
        Button play = (Button) findViewById(R.id.play);
        final Button pause = (Button) findViewById(R.id.pause);
        Button stop = (Button) findViewById(R.id.stop);
        play.setOnClickListener( new OnClickListener() {
            @Override
            public void onClick(View v) {
                mp.reset();
                try {
                    mp.setDataSource( "/sdcard/1.mp4");
                    mp.setDisplay( sv.getHolder());
                    mp.prepare();
                    mp.start();
                    pause.setText( "暂停");
                    pause.setEnabled( true);
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (SecurityException e) {
                    e.printStackTrace();
                } catch (IllegalStateException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        stop.setOnClickListener( new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mp.isPlaying()) {
                    mp.stop();
                    pause.setEnabled( false);
                }
            }
        });
        pause.setOnClickListener( new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mp.isPlaying()) {
                    mp.pause();
                    ((Button) v).setText( "继续");
                } else {
                    mp.start();
                    ((Button) v).setText( "暂停");
                }
            }
        });
        mp.setOnCompletionListener( new OnCompletionListener() {
            @Override
            public voidonCompletion(MediaPlayer mp) {
                Toast.makeText(MainActivity. this, "视频播放完毕!", Toast.LENGTH_SHORT ).show();
            }
        });
    }
    @Override
    protected void onDestroy() {
        if (mp.isPlaying()) {
            mp.stop();
        }
        mp.release();
        super.onDestroy();
    }
}

SurfaceView介绍

基本介绍

先来介绍一下大部分软件如何解析一段视频流。首先它需要先确定视频的格式,这个和解码相关,不同的格式视频编码不同,不是这里的重点。知道了视频的编码格式后,再通过编码格式进行解码,最后得到一帧一帧的图像,并把这些图像快速的显示在界面上,即为播放一段视频。SurfaceView在Android中就是完成这个功能的。

既然SurfaceView是配合MediaPlayer使用的,MediaPlayer也提供了相应的方法设置SurfaceView显示图片,只需要为MediaPlayer指定SurfaceView显示图像即可。它的完整签名如下:

     
1
     
void setDisplay(SurfaceHolder sh)

它需要传递一个SurfaceHolder对象,SurfaceHolder可以理解为SurfaceView装载需要显示的一帧帧图像的容器,它可以通过SurfaceHolder.getHolder()方法获得。
使用MediaPlayer配合SurfaceView播放视频的步骤与播放使用MediaPlayer播放MP3大体一致,只需要额外设置显示的SurfaceView即可。

缓冲原理介绍

上面有提到,SurfaceView和大部分视频应用一样,把视频流解析成一帧帧的图像进行显示,但是如果把这个解析的过程放到一个线程中完成,可能在上一帧图像已经显示过后,下一帧图像还没有来得及解析,这样会导致画面的不流畅或者声音和视频不同步的问题。

所以SurfaceView和大部分视频应用一样,通过双缓冲的机制来显示帧图像。

那么什么是双缓冲呢?双缓冲可以理解为有两个线程轮番去解析视频流的帧图像,当一个线程解析完帧图像后,把图像渲染到界面中,同时另一线程开始解析下一帧图像,使得两个线程轮番配合去解析视频流,以达到流畅播放的效果。

SurfaceHolder

SurfaceView内部实现了双缓冲的机制,但是实现这个功能是非常消耗系统内存的。因为移动设备的局限性,Android在设计的时候规定,SurfaceView如果为用户可见的时候,创建SurfaceView的SurfaceHolder用于显示视频流解析的帧图片,如发现SurfaceView变为用户不可见的时候,则立即销毁SurfaceView的SurfaceHolder,以达到节约系统资源的目的。

如果开发人员不对SurfaceHolder进行维护,会出现最小化程序后,再打开应用的时候,视频的声音在继续播放,但是不显示画面了的情况,这就是因为当SurfaceView不被用户可见的时候,之前的SurfaceHolder已经被销毁了,再次进入的时候,界面上的SurfaceHolder已经是新的SurfaceHolder了。

所以SurfaceHolder需要我们开发人员去编码维护,维护SurfaceHolder需要用到它的一个回调,SurfaceHolder.Callback(),它需要实现三个如下三个方法:

     
1
2
3
     
void surfaceDestroyed(SurfaceHolder holder):当SurfaceHolder被销毁的时候回调。
void surfaceCreated (SurfaceHolder holder):当SurfaceHolder被创建的时候回调。
void surfaceChange (SurfaceHolder holder):当SurfaceHolder的尺寸发生变化的时候被回调。

以下是这三个方法的调用过程,在应用中分别为SurfaceHolder实现了这三个方法,先进入应用,SurfaceHolder被创建,创建好之后会改变SurfaceHolder的大小,然后按Home键回退到桌面销毁SurfaceHolder,最后再进入应用,重新SurfaceHolder并改变其大小。

扩展

MediaStore

MediaStore这个类是android系统提供的一个多媒体数据库,android中多媒体信息都可以从这里提取。这个MediaStore包括了多媒体数据库的所有信息,包括音频,视频和图像,android把所有的多媒体数据库接口进行了封装,所有的数据库不用自己进行创建,直接调用利用ContentResolver去调用那些封装好的接口就可以进行数据库的操作,多媒体数据库的使用方法和SQLITE3的方法是一样的。

MediaStore中的数据是在MediaScanner扫描后通过MediaProvider中的一个service进行更新的。

MediaScanner

在Android系统中,多媒体库是通过MediaScanner去扫描磁盘文件,对元信息的处理,并通过MediaProvider保存到MediaStore中。
MediaScanner可以通过手动控制,在ANDROID系统中,已经定制了三种事件会触发MediaScanner去扫描磁盘文件:

ACTION_BOOT_COMPLETED【系统启动完后发出这个消息】
ACTION_MEDIA_MOUNTED 【插卡事件触发的消息】
ACTION_MEDIA_SCANNER_SCAN_FILE【一般是在一些文件操作后,开发人员手动发出的一个重新扫描多媒体文件的消息】

发送消息通过sendBroadcast函数完成,比如广播一个ACTION_MEDIA_MOUNTED消息:

     
1
     
sendBroadcast( new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse( "file://"+ Environment.getExternalStorageDirectory())));

视频编解码

微校中采用的是FFmpeg开源框架进行视频的解码处理。FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。

FFmpeg项目的名称来自MPEG视频编码标准,前面的”FF”代表”Fast Forward”。

其实电影文件有很多基本的组成部分。首先,文件本身被称为容器Container,容器的类型决定了文件信息存放的位置。接着,你有一组流,例如,你经常有的是一个音频流和一个视频流。在流中的数据元素被称为帧Frame。每个流是由不同的编码器来编码生成的。接着从流中被读出来的叫做包Packets。包是一段数据,它包含了一段可以被解码成方便我们最后在应用程序中操作的原始帧的数据。

我们可以利用GraphEdit工具对视频文件进行过滤,看到其中的解码思路。大致分为七个模块分别为:读文件模块,解复用模块,视频解码模块,音频解码音频,颜色空间转换模块,视频显示模块(显卡),音频播放模块(声卡)。

对应具体的代码实现,粗略的分为五类,分别是Source filter, Demux flter, Decoder filter, Color Space converter filter,Render filter

Source filter 源过滤器的作用是为下级 demux filter 以包的形式源源不断的提供数据流。Sourcefilter 另外一个作用就是屏蔽读本地文件和获取网络数据的差别,在下一级的 demux filter 看来,本地文件和网络数据是一样的。

Demux flter解复用过滤器的作用是识别文件类型,媒体类型,分离出各媒体原始数据流,打上时钟信息后送给下级 decoder filter。在本例中,AVI Splitter 是 Demux filter。

Decoder filter解码过滤器的作用就是解码数据包,并且把同步时钟信息传递下去。对视频媒体而言,通常是解码成 YUV 数据,然后利用显卡直接支持 YUV 格式数据 Overlay 快速显示的特性让显卡极速显示。对音频媒体而言,通常是解码成 PCM 数据,然后送给声卡直接输出。在本例中,AVI Decompress 和 ACM Warper 是 decoder filter。

Color Space converter filter颜色空间转换过滤器的作用是把视频解码器解码出来的数据转换成当前显示系统支持的颜色格式。通常视频解码器解码出来的是 YUV 数据,PC 系统是直接支持 YUV 格式的,也支持 RGB 格式,有些嵌入式系统只支持 RGB 格式的。在本例中,视频解码器解码出来的是 RGB8 格式的数据,Color space converter filter 把 RGB8 转换成 RGB32 显示。

Render filter渲染过滤器的作用就是在适当的时间渲染相应的媒体,对视频媒体就是直接显示图像,对音频就是播放声音。视频必须要使用同步时钟信息来决定什么时候显示,而音频是可以基于视频的时钟信息通过计算进行播放。在本例中 VideoRender 和 Default DirectSound Device 是 Render filter,同时也是 Sink filter。

HLS(HTTP Live Streaming)视频直播技术

常用的流媒体协议主要有 HTTP 渐进下载和基于 RTSP/RTP 的实时流媒体协议,这二种基本是完全不同的东西,目前比较方便又好用的是 HTTP 渐进下载的方法。

处理步骤:
视频采集 ->编码器 -> 流分割 -> 普通 web 服务(索引文件和视频文件) -> 客户端

RTSP(Real Time Streaming Protocol),实时流传输协议,是TCP/IP协议体系中的一个应用层协议,由哥伦比亚大学、网景和RealNetworks公司提交的IETF RFC标准。该协议定义了一对多应用程序如何有效地通过IP网络传送多媒体数据。RTSP在体系结构上位于RTP和RTCP之上,它使用TCP或RTP完成数据传输。
HTTP与RTSP相比,HTTP传送HTML,而RTSP传送的是多媒体数据。HTTP请求由客户机发出,服务器作出响应;使用RTSP时,客户机和服务器都可以发出请求,即RTSP可以是双向的。

RTP(Real-time Transport Protocol,实时传输协议)是一个网络传输协议,它是由IETF的多媒体传输工作小组1996年在RFC 1889中公布的,后在RFC3550中进行更新。

对于视频采集,编码器首先将摄像机实时采集的音视频数据压缩编码为符合特定标准的音视频基本流,也可以用编码完了的文件,有一点必须保证,就是一定要使用H.264视频和AAC音频,因为发明这个的是苹果公司,只支持这个。然后给这些封装成为符合MPEG-2(MPEG 2 TS、MPEG2 PS之所以使用这个,主要是因为声音和视频会交织在一起,也会有关键帧来让视频可以直接播放).

流分割部分比起 RTSP 之类和普通点播的最大不同,就是他会给 MPEG-2 分割成很多个 ts 的文件。分割过程大多是按时间来切,建议切 10s 一个的文件,如果码流高可以 5 s一次。在分割还有一点不同,就是这时流分割器会生成一个含有指向这些小ts文件指针的索引文件.

所以这个文件也必须在 web 服务器上,不能少。每多 10s 时,就会多一个 ts 文件,所以索引也会跟着修改成最新的几段视频。

最后,这些切分了的小的一系列的 ts 文件,放到普通的 web 服务器中就行了。因为请求这些文件会使用标准的 HTTP 协议。索引文件后缀是.m3u8 , 索引文件采用扩展的M3U播放列表格式, 其实就一文本。

内部的视频的地址会是如下
http://media.example.com/s_96ksegment1.ts
http://media.example.com/s_96ksegment2.ts
http://media.example.com/s_96ksegment3.ts
所以这时可以直接做 Cache 和直接放到 Web 服务器中,简单方便。如果 MIME 的信息输出不对的话,记的要修改这加入 ts 和 m3u8 的后缀支持.m3u8 application/x-mpegURL .ts video/MP2T

最后就是客户端,如果是 HTML 直接在 HTML5 中直接支持这种视频可以使用如下标签

     
1
     
<video tabindex="0" height="480" width="640"> <source src="/content1/content1.m3u8"> </video>

如果是应用客户端(Safari QuickTime之类),就得装软件来支持,客户端会根据选择的流的索引来下载文件,当下载了最少二段后开始播放。直接 m3u8 的索引结束。

另外,HTTP可以设计成的自适应比特率流,在不同网络环境,选择下载不同码流的视频。

所以整个 HTTP Live Streaming 无论是直播还是点播,都能做到近似实时的方式来进行流播放。理论的最小时延是每个切片的长.


音视频终端 SDK实时互动Android 与 iOS 相关常见问题 手机专门为音乐场景设计的音量类型,音质相比于通话音量类型要好,通过音量按键可以将音量调成零。在媒体音量模式下,蓝牙耳机无法使用自带的麦克风采集声音,只能使用手机上的麦克风进行声音采集。手机专门为通话场景设计的音量类型,使用手机自带的回声抵消功能,音质相比媒体音量类型较差, 无法通过音量按键将音量调成零,但是支持蓝牙耳机上的麦克风。来监听用户离开房间事件,且该接口仅在 VideoCall 的所有用户和 LIVE 模式下的主播离开房间时会触发回调,观众离开房间不会有回调。支持,您只需选中当前工程项目,在。 阅读详情

相关推荐

基于javaweb的考研自习室的音视频通话功能设计(附完整版代码)

基于javaweb的考研自习室的音视频通话功能设计(附完整版代码)

1745

Android Video简述

Android Video简述 Video的播放实现,至于录制请参见《Android Camera简述》。 一、播放方式 1)自带播放器播放 入口Action为ACTION_VIEW,Data为Uri,Type为MIME类型。 //自带播放器播放privatevoidsysPlayer(Stringfilename...

weixin_34000916的博客 434

深度解析:安卓开发工程师进阶之路——聚焦鸿蒙、KMP与架构优化

摘要:本文深入探讨了安卓开发工程师在当前技术变革下面临的核心挑战与关键技能。文章系统分析了鸿蒙系统迁移策略、Kotlin Multiplatform跨平台开发、直播视频技术栈、多线程优化、Framework层原理等核心技术点,并提供了详细的面试题库与解析。重点阐述了如何通过分层架构和KMP实现代码复用,优化直播应用的网络传输性能,以及使用协程和Flow进行现代并发编程。文章还强调了工程能力与软技能的重要性,为开发者应对安卓生态演进和鸿蒙崛起提供了全面指导。

郑伟强dev的专栏 222

Android使用VideoView连续播放视频文件

通过以上步骤,我们可以在Android应用程序中使用VideoView组件实现连续播放视频文件的功能。首先导入视频文件,然后在布局文件中添加VideoView组件,并在Activity中初始化VideoView并设置视频源。同时监听视频播放完成事件,在事件触发时重新设置视频源,以实现连续播放视频文件的效果。希望本文对你有所帮助!如果有任何问题,请随时提问。

故事不长丨的博客 4053

直流无刷减速电机选型指南:从KV值到霍尔传感器,新手必看的5个关键参数

本文详细解析直流无刷减速电机选型的5个关键参数,包括KV值、转矩、电流与功率、减速比选择以及霍尔传感器配置。通过实战案例和避坑策略,帮助工程师精准匹配电机性能与负载需求,提升工业自动化设备的可靠性和效率。特别强调霍尔传感器在精准控制中的关键作用。

alice7model的博客 620

QT+SOEM主站协议库实战指南—从编译到嵌入式应用

本文提供了一份QT与SOEM开源EtherCAT主站协议库的集成实战指南。详细介绍了在Windows和Linux平台下编译SOEM库的步骤,并讲解了如何将其无缝集成到QT项目中,构建具备实时监控与控制功能的工业上位机应用。内容涵盖从环境搭建、核心协议开发到嵌入式部署与优化的全流程,为开发高性价比、自主可控的工业控制解决方案提供完整路径。

u6v7w8x的博客 437

Android三种播放视频的方式

在Android中,我们有三种方式来实现视频的播放: 1、使用其自带的播放器。指定Action为ACTION_VIEW,Data为Uri,Type为其MIME类型。 2、使用VideoView来播放。在布局文件中使用VideoView结合MediaController来实现对其控制。 3、使用MediaPlayer类和SurfaceView来实现,这种方式很灵活。 1、调用其自带的

鸟人如风,不舍昼夜. 3549

【Codex安装使用手册】

Codex安装部署使用教程

qq_43392230的博客 4961

Android Video

为什么80%的码农都做不了架构师?>>> ...

weixin_34232744的博客 1068

TTL转RS422(B)工业级转换模块设计:从原理到实战部署

在工业自动化与嵌入式系统中,串行通信是实现设备间数据交换的基础。其核心原理在于通过特定的电气标准与协议,将数据位序列化后进行传输。RS422作为一种差分平衡传输协议,相比常见的TTL电平,在抗共模干扰与远距离传输能力上具有显著的技术优势,这使其成为工业现场总线、楼宇自动化等对可靠性要求严苛场景的优选方案。要实现TTL设备与RS422网络的稳定互联,关键在于设计一个能够完成单端信号到差分信号高质量转换的硬件模块。这涉及到差分总线收发器芯片的选型、包含终端电阻与TVS二极管的保护电路设计,以及遵循差分走线规则的

weixin_29195615的博客 341

本地部署静态网站托管平台 Netlify 并实现外部访问(Windows 版本)

Netlify 是一款基于云计算的网站部署和托管平台,专为开发者提供便捷的静态网站和 Jamstack 应用部署服务。它的能力在于将网站构建、部署、托管以及持续集成等流程高度集成,简化了开发和运维的复杂程度。用户无需关心服务器的配置、维护等工作。本文将详细介绍如何在本地安装 Netlify 以及结合路由侠内网穿透实现外网访问 Netlify。

lyxnwct的博客 523

最新Android音视频开发进阶指南,助力打破安卓困局

2019年年末,华为5G手机的浪潮开始风靡全国,各大手机厂商也纷纷打出了5G的招牌,Android音视频开发应运而生。这两年,以高速度、泛在网、低功耗(设备端)、低时延、万物互联、重构安全为特点的5G技术使。尤其是疫情期间,远程会议、线上教育等平台的发展更是如火如荼。与此同时,Android音视频相关的开发岗位需求激增,音视频人才成为了许多企业招聘中的香饽饽。那么已经入行Android的技术员我们如何跟上时代变迁的脚步,入行音视频开发呢?

Android23333的博客 267

C# 从数秒到毫秒:MySQL+Redis百万数据优化实战全解析

在处理百万级数据时,传统单体数据库常面临查询效率低下、数据库负载过高和用户体验差等问题。为解决这些性能瓶颈,可以采用MySQL分库分表、Redis缓存和异步统计的组合策略。 数据库分片:通过水平分表和分库实现,将单表数据分散到多个物理表或数据库实例中,降低单点负载。 Redis缓存:利用Redis缓存高频数据,减少数据库查询压力,提升响应速度。 异步统计:通过消息队列(如RabbitMQ)解耦统计任务,后台处理非实时任务,避免阻塞主流程。 这些优化策略能够显著提升系统性能,确保在高并发和大数据量场景下的稳定

墨夶的博客 852

张一鸣:Android发展全靠吼,凉不凉可不是你说了算!(2)

而我以为,开源是让开发者站在巨人的肩膀上工作,开发者不必将更多的时间花在“画图纸”上,相反可以将精力放在如何优化“建筑结构”上。也正因为安卓的开源,越来越多的爱好者或者行业的精英汇聚在一起,冲撞出思维的火花。谁来与苹果相抗衡呢?塞班系统显然已无能为力,日薄西山的Windows mobile亦无能为力,未来的手机操作系统需要稳固的物理底层支持,同时也需要高度活跃、互动的生态环境。谷歌显然是有备而来。有资料显示,安卓具有以下几个突出的优势:1.安卓是个开放的平台,对第三方软件开发商也是完全开放和免费的。 2.安

2401_84520118的博客 877

vLLM 显存泄漏与 OOM 深度排查:从日志到火焰图

系统性地排查 vLLM 运行中的显存泄漏和 OOM 问题,包括 CUDA 内存快照、PyTorch 内存分析器、火焰图工具。

kaixin110a的博客 186

YOLOv8改进 | 主干篇 | 轻量级的低照度图像增强网络IAT改进YOLOv8暗光检测(全网独家首发)

本文给大家带来的改进机制是轻量级的变换器模型:Illumination Adaptive Transformer (IAT),用于图像增强和曝光校正。其基本原理是通过分解图像信号处理器(ISP)管道到局部和全局图像组件,从而恢复在低光或过/欠曝光条件下的正常光照sRGB图像。具体来说,IAT使用注意力查询来表示和调整ISP相关参数,例如颜色校正、伽马校正。模型具有约90k参数和约0.004s的处理速度,能够在低光增强和曝光校正的基准数据集上持续实现优于最新技术(State-of-The-Art, SOTA)

Snu77的博客 7959

2024年Android最新Android 音视频缓存机制的系统性设计_android 播放器缓存,2024年最新android高级面试2024

给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化学习资料的朋友,可以戳这里获取一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!习提升;**

2301_82244182的博客 756

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

安卓开发真的走向“黄昏”了吗?

前言近年来,全网关于安卓开发的讨论似乎弥漫着一种悲观情绪,不少声音开始唱衰安卓开发的前景。这不禁让人疑惑:曾经风光无限的安卓开发,为何如今却遭遇如此质疑?Android市场现状安卓系统作为全球最大的移动操作系统,其市场份额一度高达80%以上。然而,随着智能手机市场的逐渐饱和,新增用户增速放缓,对安卓开发人才的需求自然有所下...

weixin_37048832的博客 113

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

2023全新版——Android音视频开发学习指南,免费开源

目前,以高速度、泛在网、低功耗(设备端)、低时延、万物互联、重构安全为特点的5G技术发展迅猛,各类**直播、短视频**等平台和相关业务也如雨后春笋般冒出,音视频开发这一行业也越来越受到重视。 音视频开发,其实就是掌握图像、音频、视频的基础知识,并且学会如何对它们进行采集、渲染、处理、传输等一系列的开发和应用。

Android23333的博客 390

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

BilibiliCacheVideoMerge:安卓用户的B站缓存合并终极解决方案

你是否曾在B站缓存了大量学习视频或娱乐内容,却发现离线观看时需要面对几十个零散的视频片段?BilibiliCacheVideoMerge正是为解决这一痛点而生的开源工具,它能智能识别并合并B站缓存文件,让你轻松获得完整的MP4视频,实现无缝离线观看体验。这款专为安卓设备设计的工具支持从Android 5.0到13的广泛系统版本,完美保留原始画质并支持弹幕挂载播放。 ## 🧩 问题发现:B站缓存

gitblog_01083的博客 267

android音视频架构

温柔一刀 879

安卓开发在AI时代何去何从?

在这个技术飞速迭代的时代,人工智能(AI)正以前所未有的速度重塑着软件开发领域。作为曾经移动互联网时代的霸主,安卓开发如今站在了AI变革的十字路口。许多开发者不禁要问:在AI浪潮的冲击下,安卓开发将何去何从?我们的职业又将面临怎样的挑战与机遇?

king的专栏 491

Android程序员的一片蓝海,5G时代必将成就的音视频开发,你废了吗

视频是什么? 音频是什么? 为什么要编码? 视频编码(格式、H264编码) 音频编码(格式、AAC编码) 音视频容器 硬解码和软解码区别 初步了解OpenGL ES 使用OpenGL渲染视频画面 OpenGL渲染多视频,实现画中画 深入了解OpenGL之EGL OpenGL FBO数据缓冲区 Android音视频硬编码:生成一个MP4 学习资料 音视频这块,目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的,在这里给大家整理了一份系统的学习.

m0_66264655的博客 1532

入门必备【Android 音视频开发进阶指南】,从小白到大佬的进阶之路

至今,短视频App可谓是家喻户晓,一片繁荣景象。随着近些年短视频的兴起,音视频开发也越来越受到重视,但是由于音视频开发涉及知识面比较广,入门门槛相对较高,让许许多多开发者望而生畏。整个音视频领域的架构以及开发已经演进了很长时间,从最开始的广电领域,到PC端的音视频领域,再到本书所介绍的移动端的音视频领域。尤其在这几年中,移动端音视频领域架构的变化是巨大的。Android音视频开发也慢慢成为许多工程师们的追求。在这里给对安卓音视频感兴趣的朋友分享一份【Android 音视频开发进阶指南。

m0_56255097的博客 666

2024年安卓最新张一鸣:Android发展全靠吼,凉不凉可不是你说了算!,字节跳动Android面试凉凉经

为了方便有学习需要的朋友,我把资料都整理成了视频教程(实际上比预期多花了不少精力)当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

2401_84562251的博客 850

作为一个Android程序员,关于音视频开发,这些你确定这些你都懂了吗?(2)

本人从事Android开发已经有十余年,算是一名资深的移动开发架构师了吧。根据我的观察发现,对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。

2401_84558406的博客 957
上一篇: 位置和传感器:传感器概览
下一篇: 位置和传感器:运动传感器
一世豁然
博客等级 码龄15年 1276粉丝 146原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值