当前位置: 首页 > news >正文

音视频入门之音频采集、编码、播放

作者:花海blog

今天我们学习音频的采集、编码、生成文件、转码等操作,我们生成三种格式的文件格式,pcm、wav、aac 三种格式,并且我们用 AudioStack 来播放音频,最后我们播放这个音频。

使用 AudioRecord 实现录音生成PCM 文件

AudioRecord 是 Android 系统提供的用于实现录音的功能类,要想了解这个类的具体的说明和用法,我们可以去看一下官方的文档:

AndioRecord类的主要功能是让各种 Java 应用能够管理音频资源,以便它们通过此类能够录制声音相关的硬件所收集的声音。此功能的实现就是通过”pulling”(读取)AudioRecord对象的声音数据来完成的。在录音过程中,应用所需要做的就是通过后面三个类方法中的一个去及时地获取AudioRecord对象的录音数据. AudioRecord类提供的三个获取声音数据的方法分别是read(byte[], int, int), read(short[], int, int), read(ByteBuffer, int). 无论选择使用那一个方法都必须事先设定方便用户的声音数据的存储格式。

开始录音的时候,AudioRecord需要初始化一个相关联的声音buffer, 这个buffer主要是用来保存新的声音数据。这个buffer的大小,我们可以在对象构造期间去指定。它表明一个AudioRecord对象还没有被读取(同步)声音数据前能录多长的音(即一次可以录制的声音容量)。声音数据从音频硬件中被读出,数据大小不超过整个录音数据的大小(可以分多次读出),即每次读取初始化buffer容量的数据。

1.1 首先要声明一些全局的变量和常量参数

主要是声明一些用到的参数,具体解释可以看注释。

//指定音频源 这个和MediaRecorder是相同的 MediaRecorder.AudioSource.MIC指的是麦克风
private static final int mAudioSource = MediaRecorder.AudioSource.MIC;
//指定采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)
private static final int mSampleRateInHz = 44100;
//指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量,单声道
private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;//指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制,它实际上是原始音频样本。
//因此可以设置每个样本的分辨率为16位或者8位,16位将占用更多的空间和处理能力,表示的音频也更加接近真实。
private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
//指定缓冲区大小。调用AudioRecord类的getMinBufferSize方法可以获得。
private int mBufferSizeInBytes;
// 声明 AudioRecord 对象
private AudioRecord mAudioRecord = null;

1.2 获取buffer的大小并创建AudioRecord

//初始化数据,计算最小缓冲区
mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);
//创建AudioRecorder对象mAudioRecord = new AudioRecord(mAudioSource, mSampleRateInHz, mChannelConfig,mAudioFormat, mBufferSizeInBytes);

1.3 创建一个子线程开启线程录音,并写入文件文件

 @Overridepublic void run() {//标记为开始采集状态isRecording = true;//创建文件createFile();try {//判断AudioRecord未初始化,停止录音的时候释放了,状态就为STATE_UNINITIALIZEDif (mAudioRecord.getState() == mAudioRecord.STATE_UNINITIALIZED) {initData();}//最小缓冲区byte[] buffer = new byte[mBufferSizeInBytes];//获取到文件的数据流mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));//开始录音mAudioRecord.startRecording();//getRecordingState获取当前AudioReroding是否正在采集数据的状态while (isRecording && mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {int bufferReadResult = mAudioRecord.read(buffer, 0, mBufferSizeInBytes);for (int i = 0; i < bufferReadResult; i++) {mDataOutputStream.write(buffer[i]);}}} catch (Exception e) {Log.e(TAG, "Recording Failed");} finally {// 停止录音stopRecord();IOUtil.close(mDataOutputStream);}}

1.4 权限和采集小结

注意:权限需求:WRITE_EXTERNAL_STORAGE、RECORD_AUDIO 到现在基本的录音的流程就介绍完了,但是这时候问题来了:

1.我按照流程,把音频数据都输出到文件里面了,停止录音后,打开此文件,发现不能播放,到底是为什么呢?

答:按照流程走完了,数据是进去了,但是现在的文件里面的内容仅仅是最原始的音频数据,术语称为raw(中文解释是“原材料”或“未经处理的东西”),这时候,你让播放器去打开,它既不知道保存的格式是什么,又不知道如何进行解码操作。当然播放不了。

2.那如何才能在播放器中播放我录制的内容呢?

答: 在文件的数据开头加入AAC HEAD 或者 AAC 数据即可,也就是文件头。只有加上文件头部的数据,播放器才能正确的知道里面的内容到底是什么,进而能够正常的解析并播放里面的内容。

PCM 、WAV、AAC 的文件头介绍

我这里简单的介绍一下这三种的格式的基本介绍,具体我添加了具体的访问链接,具体点击详情查看,我这里点到为止。

PCM: PCM(Pulse Code Modulation----脉码调制录音)。所谓PCM录音就是将声音等模拟信号变成符号化的脉冲列,再予以记录。PCM信号是由[1]、[0]等符号构成的数字信号,而未经过任何编码和压缩处理。与模拟信号比,它不易受传送系统的杂波及失真的影响。动态范围宽,可得到音质相当好的影响效果。

WAV : wav是一种无损的音频文件格式,WAV符合 PIFF(Resource Interchange File Format)规范。所有的WAV都有一个文件头,这个文件头音频流的编码参数。WAV对音频流的编码没有硬性规定,除了PCM之外,还有几乎所有支持ACM规范的编码都可以为WAV的音频流进行编码。 简单来说:WAV 是一种无损的音频文件格式,PCM是没有压缩的编码方式

AAC : AAC(Advanced Audio Coding),中文称为“高级音频编码”,出现于1997年,基于 MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、AT&T、Sony(索尼)等公司共同开发,目的是取代MP3格式。2000年,MPEG-4标准出现后,AAC 重新集成了其特性,加入了SBR技术和PS技术,为了区别于传统的 MPEG-2 AAC 又称为 MPEG-4 AAC。他是一种专为声音数据设计的文件压缩格式,与Mp3类似。利用AAC格式,可使声音文件明显减小,而不会让人感觉声音质量有所降低 。

PCM 转化为 WAV

在文件的数据开头加入WAVE HEAD 或者 AAC 数据即可,也就是文件头。只有加上文件头部的数据,播放器才能正确的知道里面的内容到底是什么,进而能够正常的解析并播放里面的内容。具体的头文件的描述,在Play a WAV file on an AudioTrack里面可以进行了解。

public class WAVUtil {/*** PCM文件转WAV文件** @param inPcmFilePath  输入PCM文件路径* @param outWavFilePath 输出WAV文件路径* @param sampleRate     采样率,例如44100* @param channels       声道数 单声道:1或双声道:2* @param bitNum         采样位数,8或16*/    
public static void convertPcm2Wav(String inPcmFilePath, String outWavFilePath, int sampleRate,int channels, int bitNum) {FileInputStream in = null;FileOutputStream out = null;byte[] data = new byte[1024];try {//采样字节byte率long byteRate = sampleRate * channels * bitNum / 8;in = new FileInputStream(inPcmFilePath);out = new FileOutputStream(outWavFilePath);//PCM文件大小long totalAudioLen = in.getChannel().size();//总大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小long totalDataLen = totalAudioLen + 36;writeWaveFileHeader(out, totalAudioLen, totalDataLen, sampleRate, channels, byteRate);int length = 0;while ((length = in.read(data)) > 0) {out.write(data, 0, length);}} catch (Exception e) {e.printStackTrace();} finally {IOUtil.close(in,out);}}/*** 输出WAV文件** @param out           WAV输出文件流* @param totalAudioLen 整个音频PCM数据大小* @param totalDataLen  整个数据大小* @param sampleRate    采样率* @param channels      声道数* @param byteRate      采样字节byte率* @throws IOException*/private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,long totalDataLen, int sampleRate, int channels, long byteRate) throws IOException {byte[] header = new byte[44];header[0] = 'R'; // RIFFheader[1] = 'I';header[2] = 'F';header[3] = 'F';header[4] = (byte) (totalDataLen & 0xff);//数据大小header[5] = (byte) ((totalDataLen >> 8) & 0xff);header[6] = (byte) ((totalDataLen >> 16) & 0xff);header[7] = (byte) ((totalDataLen >> 24) & 0xff);header[8] = 'W';//WAVEheader[9] = 'A';header[10] = 'V';header[11] = 'E';//FMT Chunkheader[12] = 'f'; // 'fmt 'header[13] = 'm';header[14] = 't';header[15] = ' ';//过渡字节//数据大小header[16] = 16;// 4 bytes: size of 'fmt ' chunkheader[17] = 0;header[18] = 0;header[19] = 0;//编码方式 10H为PCM编码格式header[20] = 1; // format = 1header[21] = 0;//通道数header[22] = (byte) channels;header[23] = 0;//采样率,每个通道的播放速度header[24] = (byte) (sampleRate & 0xff);header[25] = (byte) ((sampleRate >> 8) & 0xff);header[26] = (byte) ((sampleRate >> 16) & 0xff);header[27] = (byte) ((sampleRate >> 24) & 0xff);//音频数据传送速率,采样率*通道数*采样深度/8header[28] = (byte) (byteRate & 0xff);header[29] = (byte) ((byteRate >> 8) & 0xff);header[30] = (byte) ((byteRate >> 16) & 0xff);header[31] = (byte) ((byteRate >> 24) & 0xff);// 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数header[32] = (byte) (channels * 16 / 8);header[33] = 0;//每个样本的数据位数header[34] = 16;header[35] = 0;//Data chunkheader[36] = 'd';//dataheader[37] = 'a';header[38] = 't';header[39] = 'a';header[40] = (byte) (totalAudioLen & 0xff);header[41] = (byte) ((totalAudioLen >> 8) & 0xff);header[42] = (byte) ((totalAudioLen >> 16) & 0xff);header[43] = (byte) ((totalAudioLen >> 24) & 0xff);out.write(header, 0, 44);}
}

看到下图我们生成了相对的 wav 文件,我们用用本机自带播放器打开此时就能正常播放,但是我们发现他的大小比较大,我们看到就是几分钟就这么大,我们平时用的是 mp3 、aac 格式的,我们如何办到的呢,这里我们继续看一下 mp3 格式如何能生成 。

PCM 转化为 AAC 文件格式

生成 aac 文件播放

public class AACUtil {.../*** 初始化AAC编码器*/    private void initAACMediaEncode() {try {//参数对应-> mime type、采样率、声道数MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 16000, 1);encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64000);//比特率encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1024);//作用于inputBuffer的大小mediaEncode = MediaCodec.createEncoderByType(encodeType);mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);} catch (IOException e) {e.printStackTrace();}if (mediaEncode == null) {LogUtil.e("create mediaEncode failed");return;}mediaEncode.start();encodeInputBuffers = mediaEncode.getInputBuffers();encodeOutputBuffers = mediaEncode.getOutputBuffers();encodeBufferInfo = new MediaCodec.BufferInfo();}private boolean codeOver = false;/*** 开始转码* 音频数据{@link #srcPath}先解码成PCM  PCM数据在编码成MediaFormat.MIMETYPE_AUDIO_AAC音频格式* mp3->PCM->aac*/    public void startAsync() {LogUtil.w("start");new Thread(new DecodeRunnable()).start();}/*** 解码{@link #srcPath}音频文件 得到PCM数据块** @return 是否解码完所有数据*/private void srcAudioFormatToPCM() {File file = new File(srcPath);// 指定要读取的文件FileInputStream fio = null;try {fio = new FileInputStream(file);byte[] bb = new byte[1024];while (!codeOver) {if (fio.read(bb) != -1) {LogUtil.e("============   putPCMData ============" + bb.length);dstAudioFormatFromPCM(bb);} else {codeOver = true;}}fio.close();} catch (Exception e) {e.printStackTrace();}}private byte[] chunkAudio = new byte[0];/*** 编码PCM数据 得到AAC格式的音频文件*/    private void dstAudioFormatFromPCM(byte[] pcmData) {int inputIndex;ByteBuffer inputBuffer;int outputIndex;ByteBuffer outputBuffer;int outBitSize;int outPacketSize;byte[] PCMAudio;PCMAudio = pcmData;encodeInputBuffers = mediaEncode.getInputBuffers();encodeOutputBuffers = mediaEncode.getOutputBuffers();encodeBufferInfo = new MediaCodec.BufferInfo();inputIndex = mediaEncode.dequeueInputBuffer(0);inputBuffer = encodeInputBuffers[inputIndex];inputBuffer.clear();inputBuffer.limit(PCMAudio.length);inputBuffer.put(PCMAudio);//PCM数据填充给inputBuffermediaEncode.queueInputBuffer(inputIndex, 0, PCMAudio.length, 0, 0);//通知编码器 编码outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);while (outputIndex > 0) {outBitSize = encodeBufferInfo.size;outPacketSize = outBitSize + 7;//7为ADT头部的大小outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出BufferoutputBuffer.position(encodeBufferInfo.offset);outputBuffer.limit(encodeBufferInfo.offset + outBitSize);chunkAudio = new byte[outPacketSize];addADTStoPacket(chunkAudio, outPacketSize);//添加ADTSoutputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中try {//录制aac音频文件,保存在手机内存中bos.write(chunkAudio, 0, chunkAudio.length);bos.flush();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}            outputBuffer.position(encodeBufferInfo.offset);mediaEncode.releaseOutputBuffer(outputIndex, false);outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);}}/*** 添加ADTS头** @param packet* @param packetLen*/    
private void addADTStoPacket(byte[] packet, int packetLen) {int profile = 2; // AAC LCint freqIdx = 8; // 16KHzint chanCfg = 1; // CPE// fill in ADTS datapacket[0] = (byte) 0xFF;packet[1] = (byte) 0xF1;packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));packet[4] = (byte) ((packetLen & 0x7FF) >> 3);packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);packet[6] = (byte) 0xFC;}/*** 释放资源*/    
public void release() {...}/*** 解码线程*/    
private class DecodeRunnable implements Runnable {@Overridepublic void run() {srcAudioFormatToPCM();}}
}

AudioStack 播放

AudioTrack 类可以完成Android平台上音频数据的输出任务。AudioTrack有两种数据加载模式(MODE_STREAM和MODE_STATIC),对应的是数据加载模式和音频流类型, 对应着两种完全不同的使用场景。

MODE_STREAM: 在这种模式下,通过write一次次把音频数据写到AudioTrack中。这和平时通过write系统调用往文件中写数据类似,但这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引入延时。为解决这一问题,AudioTrack就引入了第二种模式。

MODE_STATIC: 这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用量较小,延时要求较高的文件。但它也有一个缺点,就是一次write的数据不能太多,否则系统无法分配足够的内存来存储全部数据。

播放声音可以用MediaPlayer和AudioTrack,两者都提供了Java API供应用开发者使用。虽然都可以播放声音,但两者还是有很大的区别的,其中最大的区别是MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer会在framework层创建对应的音频解码器。而AudioTrack只能播放已经解码的PCM流,如果对比支持的文件格式的话则是AudioTrack只支持wav格式的音频文件,因为wav格式的音频文件大部分都是PCM流。AudioTrack不创建解码器,所以只能播放不需要解码的wav文件。

3.1 音频流的类型

在AudioTrack构造函数中,会接触到AudioManager.STREAM_MUSIC这个参数。它的含义与Android系统对音频流的管理和分类有关。

Android将系统的声音分为好几种流类型,下面是几个常见的:
STREAM_ALARM:警告声STREAM_MUSIC:音乐声,例如music等STREAM_RING:铃声STREAM_SYSTEM:系统声音,例如低电提示音,锁屏音等STREAM_VOCIE_CALL:通话声

注意:上面这些类型的划分和音频数据本身并没有关系。例如MUSIC和RING类型都可以是某首MP3歌曲。另外,声音流类型的选择没有固定的标准,例如,铃声预览中的铃声可以设置为MUSIC类型。音频流类型的划分和Audio系统对音频的管理策略有关。

3.2 Buffer分配和Frame的概念

在计算Buffer分配的大小的时候,我们经常用到的一个方法就是:getMinBufferSize。这个函数决定了应用层分配多大的数据Buffer。

AudioTrack.getMinBufferSize(8000,//每秒8K个采样点AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道AudioFormat.ENCODING_PCM_16BIT);

从AudioTrack.getMinBufferSize开始追溯代码,可以发现在底层的代码中有一个很重要的概念:Frame(帧)。Frame是一个单位,用来描述数据量的多少。1单位的Frame等于1个采样点的字节数×声道数(比如PCM16,双声道的1个Frame等于2×2=4字节)。1个采样点只针对一个声道,而实际上可能会有一或多个声道。由于不能用一个独立的单位来表示全部声道一次采样的数据量,也就引出了Frame的概念。Frame的大小,就是一个采样点的字节数×声道数。另外,在目前的声卡驱动程序中,其内部缓冲区也是采用Frame作为单位来分配和管理的。

getMinBufSize会综合考虑硬件的情况(诸如是否支持采样率,硬件本身的延迟情况等)后,得出一个最小缓冲区的大小。一般我们分配的缓冲大小会是它的整数倍。

3.3 构建过程

每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中进行播放,目前Android同时最多可以创建32个音频流,也就是说,Mixer最多会同时处理32个AudioTrack的数据流。

3.4 Show Me The Code

public class AudioTrackManager {...//音频流类型private static final int mStreamType = AudioManager.STREAM_MUSIC;//指定采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)private static final int mSampleRateInHz = 44100;//指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; //单声道//指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制,它实际上是原始音频样本。//因此可以设置每个样本的分辨率为16位或者8位,16位将占用更多的空间和处理能力,表示的音频也更加接近真实。private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;//指定缓冲区大小。调用AudioRecord类的getMinBufferSize方法可以获得。private int mMinBufferSize;//STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。这个和我们在socket中发送数据一样,// 应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。private static int mMode = AudioTrack.MODE_STREAM;private void initData() {//根据采样率,采样精度,单双声道来得到frame的大小。mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);//计算最小缓冲区//注意,按照数字音频的知识,这个算出来的是一秒钟buffer的大小。//创建AudioTrackmAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig,mAudioFormat, mMinBufferSize, mMode);}/*** 启动播放线程*/private void startThread() {destroyThread();isStart = true;if (mRecordThread == null) {mRecordThread = new Thread(recordRunnable);mRecordThread.start();}}/*** 播放线程*/private Runnable recordRunnable = new Runnable() {@Overridepublic void run() {try {//设置线程的优先级android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);byte[] tempBuffer = new byte[mMinBufferSize];int readCount = 0;while (mDis.available() > 0) {readCount = mDis.read(tempBuffer);if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {continue;}//一边播放一边写入语音数据if (readCount != 0 && readCount != -1) {//判断AudioTrack未初始化,停止播放的时候释放了,状态就为STATE_UNINITIALIZEDif (mAudioTrack.getState() == mAudioTrack.STATE_UNINITIALIZED) {initData();}mAudioTrack.play();mAudioTrack.write(tempBuffer, 0, readCount);                    }}//播放完就停止播放stopPlay();} catch (Exception e) {e.printStackTrace();}}};/*** 启动播放** @param path*/    
public void startPlay(String path) {try {setPath(path);startThread();} catch (Exception e) {e.printStackTrace();}}/*** 停止播放*/    
public void stopPlay() {try {destroyThread();//销毁线程if (mAudioTrack != null) {if (mAudioTrack.getState() == AudioRecord.STATE_INITIALIZED) {//初始化成功mAudioTrack.stop();//停止播放}if (mAudioTrack != null) {mAudioTrack.release();//释放audioTrack资源}}if (mDis != null) {mDis.close();//关闭数据输入流}} catch (Exception e) {e.printStackTrace();}}
}

目前市场上对于音视频人才的需求,市场招聘音视频岗位薪资亦是水涨船高,但音视频开发这块目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的。只能通过一点点的学习和积累把这块的知识串联积累起来。

这里给大家推荐一套学习路线,并附有相关《音视频开发核心知识点笔记》及《音视频源码解析》和配套视频,相信可以给大家提供一些帮助,都已经进行了整理好了:https://qr18.cn/Ei3VPD

音视频初级入门:https://qr18.cn/Ei3VPD

音视频初级入门主要是接触Android多媒体展示相关的API,通过单独的列举和使用这些API,对Android音视频处理有一个基本的轮廓,虽然知识点相对来说是比较散的,但是点成线、线成面,基本的基础掌握了,通过学习Android音视频核心的API将音视频的流程串联起来,这样对于音视频的了解和控制就不仅仅局限于最外层的API了,而是能够通过相对底层的方式来加深对Android 音视频开发的认知。

音视频中级进阶:OpenSL ES 学习

学习 Android 平台 OpenSL ES API,了解 OpenSL 开发的基本流程,使用OpenSL播放PCM数据,并了解相关API的简单使用

音视频高级探究:https://qr18.cn/Ei3VPD

音视频编解码技术

流媒体协议

多媒体文件格式

FFmpeg 学习:https://qr18.cn/Ei3VPD

相关文章:

音视频入门之音频采集、编码、播放

作者&#xff1a;花海blog 今天我们学习音频的采集、编码、生成文件、转码等操作&#xff0c;我们生成三种格式的文件格式&#xff0c;pcm、wav、aac 三种格式&#xff0c;并且我们用 AudioStack 来播放音频&#xff0c;最后我们播放这个音频。 使用 AudioRecord 实现录音生成…...

在 Linux 系统中,如何发起POST/GET请求

在 Linux 系统中&#xff0c;可以使用命令行工具 curl 或者 wget 来发送 POST 请求。这两个工具都是非常常用的命令行工具&#xff0c;可以通过命令行直接发送 HTTP 请求。 1. 使用 curl 发送 POST 请求&#xff1a; curl -X POST -H "Content-Type: application/json&q…...

文心一言大数据模型-文心千帆大模型平台

官网&#xff1a; 文心千帆大模型平台 (baidu.com) 文心千帆大模型 (baidu.com) 模型优势 1、模型效果优&#xff1a;所需标注数据少&#xff0c;在各场景上的效果处于业界领先水平 2、生成能力强&#xff1a;拥有丰富的AI内容生成&#xff08;AIGC&#xff09;能力 3、应用…...

django学习笔记(1)

django创建项目 先创建一个文件夹用来放django的项目&#xff0c;我这里是My_Django_it 之后打开到该文件下&#xff0c;并用下面的指令来创建myDjango1项目 D:\>cd My_Django_itD:\My_Django_it>"D:\zzu_it\Django_learn\Scripts\django-admin.exe" startpr…...

postgresql主从搭建

postgresql主从搭建 主从服务器分别安装好postgresql 主库 创建数据库热备帐号replica&#xff0c;密码123456为例&#xff0c;则执行以下命令 create role replica login replication encrypted password 123456;打开 pg_hba.conf 配置文件&#xff0c;设置 replica 用户白…...

将Parasoft和ChatGPT相结合会如何?

ChatGPT是2023年最热门的话题之一&#xff0c;是OpenAI训练的语言模型。它能够理解和生成自然语言文本&#xff0c;并接受过大量数据的训练&#xff0c;包括用各种编程语言编写的许多开源项目的源代码。 软件开发人员可以利用大量的知识库来协助他们的工作&#xff0c;因为它具…...

Go text/template详解:使用指南与最佳实践

I. 简介 A. 什么是 Go text/template Go text/template 是 Go 语言标准库中的一个模板引擎&#xff0c;用于生成文本输出。它使用类似于 HTML 的模板语言&#xff0c;可以将数据和模板结合起来&#xff0c;生成最终的文本输出。 B. Go text/template 的优点 Go text/templa…...

Stable Diffusion在各种显卡上的加速方式测试,最高可以提速211.2%

Stable Diffusion是一种基于扩散模型的图像生成技术&#xff0c;能够从文本生成高质量的图像&#xff0c;适用于CG&#xff0c;插图和高分辨率壁纸等领域。 但是它计算过程复杂&#xff0c;使得它的生成速度较慢。所以研究人员就创造了各种提高其速度的方式&#xff0c;比如Xf…...

Java读取外链图片忽略ssl验证转为base64

最近在对接外部接口时遇到返回的图片所在的服务器全都没有ssl证书&#xff0c;导致在前端直接用img标签展示时图片开裂。于是转为通过后端获取&#xff0c;绕过ssl验证之后转为base64返回。记录一下代码段。 package com.sy.ai.common.utils;import cn.hutool.core.codec.Base…...

系统架构设计师 10:软件架构的演化和维护

一、软件架构演化 如果软件架构的定义是 SA{components, connectors, constraints}&#xff0c;也就是说&#xff0c;软件架构包括组件、连接件和约束三大要素&#xff0c;这类软件架构演化主要关注的就是组件、连接件和约束的添加、修改与删除等。 二、面向对象软件架构演化…...

Windows 11 绕过 TPM 方法总结,通用免 TPM 镜像下载 (2023 年 7 月更新)

Windows 11 绕过 TPM 方法总结&#xff0c;通用免 TPM 镜像下载 (2023 年 7 月更新) 在虚拟机、Mac 电脑和 TPM 不符合要求的旧电脑上安装 Windows 11 的通用方法总结 请访问原文链接&#xff1a;https://sysin.org/blog/windows-11-no-tpm/&#xff0c;查看最新版。原创作品…...

EXCEL,如何比较2个表里的数据差异(使用数据透视表)

目录 1 问题: 需要比较如下2个表的内容差异 1.1 原始数据喝问题 1.2 提前总结 2 使用EXCEL公式方法 2.1 新增辅助列&#xff1a; 辅助index 2.2 具体公式 配合条件格式 使用 3 数据透视表方法 3.1 新增辅助列&#xff1a; 辅助index 3.2 需要先打开 数据透视表向导 …...

字节抖音小程序,使用 uniapp 调起内置支付

字节抖音小程序&#xff0c;使用 uniapp 调起内置支付 第一步&#xff1a;提交订单 后端通过抖音预下单接口&#xff0c;提交支付订单信息。 预下单接口_小程序_抖音开放平台预下单接口 提交支付订单信息。 ## 使用限制 无 ## 接口说明 预下单接口需要保证同一app_id下每笔订…...

django模板继承和组件了解

1、模板继承 什么时候需要用到模板呢&#xff0c;比如我们在开发的页面的导航栏&#xff0c;你点不同的功能页面这个导航栏都是一样的&#xff0c;如果每个页面都要加上这个导航条会写重复代码&#xff0c;而且如果导航条有变化&#xff0c;每个页面都要修改&#xff0c;这个是…...

首屏优化,给以图片为背景的元素增加相似背景,优化用户体验,background-image 绘制规则

每日鸡汤&#xff1a;每个你想要学习的瞬间都是未来的你向自己求救 假设你的项目首页有个大大的图片作为背景&#xff0c;那么这个图片肯定会在网络不好的时候加载出来很慢&#xff0c;导致用户回看到一大片白屏&#xff0c;这样很影响体验。这也是老生常谈的首屏优化的问题。例…...

【用户体验分析报告】 按需加载组件,导致组件渲染卡顿,影响交互体验?组件拆包预加载方案来了!

首先&#xff0c;我们看一些针对《如何提升应用首屏加载体验》的文章&#xff0c;提到的必不可少的措施&#xff0c;便是减少首屏幕加载资源的大小&#xff0c;而减少资源大小必然会想到按需加载措施。本文提到的便是一个基于webpack 插件与 react 组件实现的一套研发高度自定义…...

idea 关闭页面右侧预览框/预览条

idea 关闭页面右侧预览框 如图&#xff0c;预览框存在想去除 找了好多方法&#xff0c;什么去掉“setting->appearance里的show editor preview tooltips”的对钩&#xff1b;又或者在该预览区的滚动条上右键&#xff0c;“取消勾选show code lens on scrollbar hover”。都…...

CSS3 Flexbox

Flex 是 Flexible Box 的缩写&#xff0c;意为弹性盒子布局。 CSS3中一种新的布局模式&#xff1a;W3C在2009年提出的一种布局方案&#xff0c;一种当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。其目的是提供一种更加有效的方式来对一个容器…...

东南大学轴承故障诊断(Python代码,CNN模型,适合复合故障诊断研究)

运行代码要求&#xff1a; 代码运行环境要求&#xff1a;Keras版本>2.4.0&#xff0c;python版本>3.6.0 本次实验主要是在两种不同工况数据下&#xff0c;进行带有复合故障的诊断实验&#xff0c;没有复合故障的诊断实验。 实验结果证明&#xff0c;针对具有复合故障的…...

ubuntu--Motrix

Motrix官网 https://motrix.app/ 适用于windows和ubuntu 资源链接 链接: https://pan.baidu.com/s/16ka-w30BXJn066absXJXCA 密码: cds2 下载上面的资源&#xff0c;打开终端&#xff0c;安装Motrix sudo dpkg -i XXX.deb 在ubuntu安装好chrome&#xff0c;然后打开设置中…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...