音视频入门之音频采集、编码、播放
作者:花海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
相关文章:

音视频入门之音频采集、编码、播放
作者:花海blog 今天我们学习音频的采集、编码、生成文件、转码等操作,我们生成三种格式的文件格式,pcm、wav、aac 三种格式,并且我们用 AudioStack 来播放音频,最后我们播放这个音频。 使用 AudioRecord 实现录音生成…...
在 Linux 系统中,如何发起POST/GET请求
在 Linux 系统中,可以使用命令行工具 curl 或者 wget 来发送 POST 请求。这两个工具都是非常常用的命令行工具,可以通过命令行直接发送 HTTP 请求。 1. 使用 curl 发送 POST 请求: curl -X POST -H "Content-Type: application/json&q…...

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

django学习笔记(1)
django创建项目 先创建一个文件夹用来放django的项目,我这里是My_Django_it 之后打开到该文件下,并用下面的指令来创建myDjango1项目 D:\>cd My_Django_itD:\My_Django_it>"D:\zzu_it\Django_learn\Scripts\django-admin.exe" startpr…...
postgresql主从搭建
postgresql主从搭建 主从服务器分别安装好postgresql 主库 创建数据库热备帐号replica,密码123456为例,则执行以下命令 create role replica login replication encrypted password 123456;打开 pg_hba.conf 配置文件,设置 replica 用户白…...

将Parasoft和ChatGPT相结合会如何?
ChatGPT是2023年最热门的话题之一,是OpenAI训练的语言模型。它能够理解和生成自然语言文本,并接受过大量数据的训练,包括用各种编程语言编写的许多开源项目的源代码。 软件开发人员可以利用大量的知识库来协助他们的工作,因为它具…...
Go text/template详解:使用指南与最佳实践
I. 简介 A. 什么是 Go text/template Go text/template 是 Go 语言标准库中的一个模板引擎,用于生成文本输出。它使用类似于 HTML 的模板语言,可以将数据和模板结合起来,生成最终的文本输出。 B. Go text/template 的优点 Go text/templa…...

Stable Diffusion在各种显卡上的加速方式测试,最高可以提速211.2%
Stable Diffusion是一种基于扩散模型的图像生成技术,能够从文本生成高质量的图像,适用于CG,插图和高分辨率壁纸等领域。 但是它计算过程复杂,使得它的生成速度较慢。所以研究人员就创造了各种提高其速度的方式,比如Xf…...
Java读取外链图片忽略ssl验证转为base64
最近在对接外部接口时遇到返回的图片所在的服务器全都没有ssl证书,导致在前端直接用img标签展示时图片开裂。于是转为通过后端获取,绕过ssl验证之后转为base64返回。记录一下代码段。 package com.sy.ai.common.utils;import cn.hutool.core.codec.Base…...

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

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

EXCEL,如何比较2个表里的数据差异(使用数据透视表)
目录 1 问题: 需要比较如下2个表的内容差异 1.1 原始数据喝问题 1.2 提前总结 2 使用EXCEL公式方法 2.1 新增辅助列: 辅助index 2.2 具体公式 配合条件格式 使用 3 数据透视表方法 3.1 新增辅助列: 辅助index 3.2 需要先打开 数据透视表向导 …...
字节抖音小程序,使用 uniapp 调起内置支付
字节抖音小程序,使用 uniapp 调起内置支付 第一步:提交订单 后端通过抖音预下单接口,提交支付订单信息。 预下单接口_小程序_抖音开放平台预下单接口 提交支付订单信息。 ## 使用限制 无 ## 接口说明 预下单接口需要保证同一app_id下每笔订…...
django模板继承和组件了解
1、模板继承 什么时候需要用到模板呢,比如我们在开发的页面的导航栏,你点不同的功能页面这个导航栏都是一样的,如果每个页面都要加上这个导航条会写重复代码,而且如果导航条有变化,每个页面都要修改,这个是…...
首屏优化,给以图片为背景的元素增加相似背景,优化用户体验,background-image 绘制规则
每日鸡汤:每个你想要学习的瞬间都是未来的你向自己求救 假设你的项目首页有个大大的图片作为背景,那么这个图片肯定会在网络不好的时候加载出来很慢,导致用户回看到一大片白屏,这样很影响体验。这也是老生常谈的首屏优化的问题。例…...

【用户体验分析报告】 按需加载组件,导致组件渲染卡顿,影响交互体验?组件拆包预加载方案来了!
首先,我们看一些针对《如何提升应用首屏加载体验》的文章,提到的必不可少的措施,便是减少首屏幕加载资源的大小,而减少资源大小必然会想到按需加载措施。本文提到的便是一个基于webpack 插件与 react 组件实现的一套研发高度自定义…...

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

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

东南大学轴承故障诊断(Python代码,CNN模型,适合复合故障诊断研究)
运行代码要求: 代码运行环境要求:Keras版本>2.4.0,python版本>3.6.0 本次实验主要是在两种不同工况数据下,进行带有复合故障的诊断实验,没有复合故障的诊断实验。 实验结果证明,针对具有复合故障的…...
ubuntu--Motrix
Motrix官网 https://motrix.app/ 适用于windows和ubuntu 资源链接 链接: https://pan.baidu.com/s/16ka-w30BXJn066absXJXCA 密码: cds2 下载上面的资源,打开终端,安装Motrix sudo dpkg -i XXX.deb 在ubuntu安装好chrome,然后打开设置中…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...

FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...