Android音视频开发(三)——MediaExtractor和MediaMuxer的使用

这篇具有很好参考价值的文章主要介绍了Android音视频开发(三)——MediaExtractor和MediaMuxer的使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

了解了音视频的编解码过程,我们接下来使用一下经常跟MediaCodec一起搭配的MediaExtractor和MediaMuxer。最后会使用一个简单的demo来了解具体了解这两个工具类的使用过程。这一节我们就先不讲MediaCodec了,放到下节的demo。

Android音视频开发(三)——MediaExtractor和MediaMuxer的使用

一、MediaExtractor

Android提供了一个MediaExtractor类,可以用来分离容器中的视频track音频track

主要API介绍:

  • setDataSource(String path):即可以设置本地文件又可以设置网络文件
  • getTrackCount():得到源文件通道数 
  • getTrackFormat(int index):获取指定(index)的通道格式
  • getSampleTime():返回当前的时间戳 
  • readSampleData(ByteBuffer byteBuf, int offset):把指定通道中的数据按偏移量读取到ByteBuffer中;
  • advance():读取下一帧数据
  • release(): 读取结束后释放资源

MediaExtractor 的使用主要有这么几步:

  1. 设置数据源
  2. 获取通道数,切换到想要的轨道
  3. 循环读取每帧的样本数据
  4. 完成后释放资源

二、MediaMuxer

MediaMuxer的作用是生成音频或视频文件;还可以把音频与视频混合成一个音视频文件。

相关API介绍:

  • MediaMuxer(String path, int format):path:输出文件的名称  format:输出文件的格式;当前只支持MP4格式;
  • addTrack(MediaFormat format):添加通道;我们更多的是使用MediaCodec.getOutpurForma()或Extractor.getTrackFormat(int index)来获取MediaFormat;也可以自己创建;
  • start():开始合成文件
  • writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo):把ByteBuffer中的数据写入到在构造器设置的文件中;
  • stop():停止合成文件
  • release():释放资源

参数

int MUXER_OUTPUT_3GPP

3GPP媒体文件格式

int MUXER_OUTPUT_HEIF

HEIF媒体文件格式

int MUXER_OUTPUT_MPEG_4

MPEG4媒体文件格式

int MUXER_OUTPUT_OGG

Ogg媒体文件格式

int MUXER_OUTPUT_WEBM

WEBM媒体文件格式

MediaMuxer的使用步骤:

  1. 设置目标文件路径和音视频格式
  2. 添加要合成的轨道,包括音轨和视轨
  3. 开始合成,循环写入每帧样本数据
  4. 完成后释放

三、MediaFormat

        用MediaCodec来进行编解码,在创建MediaCodec时需要调用configure方法进行配置,Mediaformat则是configure需要传入的一个参数。

 3.1 视频类型的Mediaformat

可以通过如下代码创建视频类型Mediaformat:

MediaFormat videoFormat = MediaFormat.createVideoFormat(videoType, width, height);

方法的参数类型:

  • videoType常用的有两种:

        MediaFormat.MIMETYPE_VIDEO_AVC(H.264
        MediaFormat.MIMETYPE_VIDEO_HEVC(H.265

  • widthheight需要根据底层支持的分辨率来设置,如果width和height设置的不符合要求会出现如下错误:
E/CameraCaptureSession: Session 1: Failed to create capture session; configuration failed

对于视频类型而言有下列四个配置是必须指定的:手动配置直接获取原视频的配置

// 指定编码器颜色格式 
videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

// 指定帧率
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);

// 指定比特率
videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 10000000);

//指定关键帧时间间隔,一般设置为每秒关键帧
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

 3.2 音频类型的Mediaformat

可以通过如下代码创建音频类型Mediaformat:

MediaFormat audioFormat = MediaFormat.createAudioFormat(audioType, sampleRate, channelCount);

方法的参数类型:

  • audioType:常用的是MediaFormat.MIMETYPE_AUDIO_AAC
  • sampleRate:采样率
  • channelCount:声道数量

单声道 channelCount=1 , 双声道 channelCount=2

对于音频类型而言有一个配置是必须指定的:

//音频比特率(码率)
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);

四、MediaCodec.BufferInfo 

        用于描述解码得到的byte[]数据的相关信息,每缓冲区元数据包括指定相关编解码器(输出)缓冲区中有效数据范围的偏移量和大小。

主要有四个属性:

  • int flags :与缓冲区关联的缓冲区标志
  • int offset :缓冲区中数据的起始偏移量
  • long presentationTimeUs :缓冲区的显示时间戳,以微秒计。这是从相应的输入缓冲区传入的表示时间戳中获得的。对于大小为0的缓冲区,应该忽略这一点。
  • int size :缓冲区中的数据量(以字节为单位)。如果这是0缓冲区中没有数据,可以丢弃。大小为0的缓冲区的唯一用途是携带流结束标记。

flags详解:

  • 与缓冲区关联的缓冲区标志。...的结合MediaCodec.BUFFER_FLAG_KEY_FRAMEMediaCodec.BUFFER_FLAG_END_OF_STREAM.
  • 作为关键帧的编码缓冲区标有MediaCodec.BUFFER_FLAG_KEY_FRAME.
  • 对应于输入缓冲区的最后一个输出缓冲区用MediaCodec.BUFFER_FLAG_END_OF_STREAM也将标有MediaCodec.BUFFER_FLAG_END_OF_STREAM。在某些情况下,这可能是一个空缓冲区,其唯一目的是携带流结束标记。

值是0或以下各项的组合MediaCodec.BUFFER_FLAG_SYNC_FRAMEMediaCodec.BUFFER_FLAG_KEY_FRAMEMediaCodec.BUFFER_FLAG_CODEC_CONFIGMediaCodec.BUFFER_FLAG_END_OF_STREAMMediaCodec.BUFFER_FLAG_PARTIAL_FRAME、以及Android . media . media codec . buffer _ FLAG _ MUXER _ DATA

  • BUFFER_FLAG_CODEC_CONFIG​  常数值:2:这表明如此标记的缓冲区包含编解码器初始化/编解码器特定数据,而不是媒体数据。
  • BUFFER_FLAG_END_OF_STREAM 常数值:4:这表示流的结束,即在此之后将没有缓冲器可用,当然,除非,flush()如下。
  • BUFFER_FLAG_KEY_FRAME 常数值:1:这表明如此标记的(编码的)缓冲区包含关键帧的数据。
  • BUFFER_FLAG_PARTIAL_FRAME 常数值:8:这表示缓冲区只包含一帧的一部分,解码器应该对数据进行批处理,直到在解码该帧之前出现一个没有该标志的缓冲区。
  • BUFFER_FLAG_SYNC_FRAME 常数值:1:这表明如此标记的(编码的)缓冲区包含关键帧的数据。API 21中不赞成使用此常量。 使用BUFFER_FLAG_KEY_FRAME相反,都是关键帧

五、MediaExtractor和MediaMuxer结合的demo

实现音视频的解封装和封装的过程:

//实现音视频的解封装和封装的过程
public class MediaCodecDemo extends Activity {

    //显示解封装后的视频和音频在SD卡保存的位置
    private TextView tv_out;
    private final String mVideoPath = Environment.getExternalStorageDirectory()
                                     + "/Pictures/送孟浩然之广陵.mp4";
    //解封装和封装在本地使用文件名
    private final String inputAudio = "audio1.aac";
    private final String outPutVideo = "video1.mp4";

    private static final String TAG1 ="解封装MediaExtractor:" ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_codec);

        initView();
        //提取视频分离出纯音频和纯视频文件
        extractorAndMuxerMP4(mVideoPath);
        //重新合成成音视频文件
        muxerMp4(inputAudio,outPutVideo);
    }

    private void initView() {
        tv_out = findViewById(R.id.tv_out);
    }
}
//提取视频分离出纯音频和纯视频文件
private void extractorAndMuxerMP4(String url){
    //提取数据(解封装)
    //1. 构造MediaExtractor
    MediaExtractor mediaExtractor = new MediaExtractor();
    try {
        //2.设置数据源,数据源可以是本地文件地址,也可以是网络地址:
        mediaExtractor.setDataSource(url);
        //3.获取轨道数
        int trackCount = mediaExtractor.getTrackCount();
        //遍历轨道,查看音频轨或者视频轨道信息
        for (int i = 0; i < trackCount; i++) {
            //4. 获取某一轨道的媒体格式
            MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
            String keyMime = trackFormat.getString(MediaFormat.KEY_MIME);
            if (TextUtils.isEmpty(keyMime)) {
               continue;
            }
            //5.通过mime信息识别音轨或视频轨道,打印相关信息
            //(默认的是先扫描到视频,在扫描到音频)
            if (keyMime.startsWith("video/")) {
                File outputFile = extractorAndMuxer(mediaExtractor, i, "/video.mp4");
                tv_out.setText("纯视频文件路径:" + outputFile.getAbsolutePath());

            } else if (keyMime.startsWith("audio/")) {
                File outputFile = extractorAndMuxer(mediaExtractor, i, "/audio.aac");
                tv_out.setText(tv_out.getText().toString() + "\n纯音频路径:" 
                                + outputFile.getAbsolutePath());
                tv_out.setVisibility(View.VISIBLE);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

        通过getTrackFormat(int index)来获取各个track的MediaFormat,通过MediaFormat来获取track的详细信息,如:MimeType、分辨率、采样频率、帧率等等

    //确定是音轨或视频轨道后,文件输出
    private File extractorAndMuxer(MediaExtractor mediaExtractor, int i, String outputName) throws IOException{
        //获取传过来的MediaExtractor对应轨道的trackFormat
        MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
        MediaMuxer mediaMuxer;
        //选择轨道
        mediaExtractor.selectTrack(i);

        File outputFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC).getAbsolutePath() + outputName);
        if (outputFile.exists()) {
            //如果文件存在,就删除
            outputFile.delete();
        }
        //1. 构造MediaMuxer
        mediaMuxer = new MediaMuxer(outputFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        //2. 添加轨道信息 参数为MediaFormat
        mediaMuxer.addTrack(trackFormat);
        //3. 开始合成
        mediaMuxer.start();
        //4. 设置buffer
        ByteBuffer buffer = ByteBuffer.allocate(500 * 1024);//设置每一帧的大小
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();

        //5.通过mediaExtractor.readSampleData读取数据流
        int sampleSize = 0;
        //循环读取每帧的样本数据
        //mediaExtractor.readSampleData(buffer, 0)把指定通道中的数据按偏移量读取到ByteBuffer中
        while ((sampleSize = mediaExtractor.readSampleData(buffer, 0)) > 0) {
            bufferInfo.flags = mediaExtractor.getSampleFlags();
            bufferInfo.offset = 0;
            bufferInfo.size = sampleSize;
            bufferInfo.presentationTimeUs = mediaExtractor.getSampleTime();
            //所有解码的帧都已渲染,我们现在可以停止播放了,虽然这里没有用到
            //一般的使用方法是判断 isEOS是否等于0;
            //int isEOS = bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM;
            //判断输出数据是否为关键帧的方法:
            //boolean keyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0;
            //6. 把通过mediaExtractor解封装的数据通过writeSampleData写入到对应的轨道
            mediaMuxer.writeSampleData(0, buffer, bufferInfo);
            //读取下一帧数据
            mediaExtractor.advance();
        }
        Log.i(TAG1, "extractorAndMuxer: " + outputName + "提取封装完成");

        mediaExtractor.unselectTrack(i);
        //6.关闭
        mediaMuxer.stop();
        mediaMuxer.release();
        return outputFile;
    }

这里需要科普一下两个正数进行&运算:两个正数进行&运算的值永远小于或等于最小的数。

if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 
    Log.i(TAG, "OutputBuffer BUFFER_FLAG_END_OF_STREAM");       
    break;                                                      
}

        我们知道BUFFER_FLAG_END_OF_STREAM代表的是2^2,也就是0100。因为是&运算,我们只用关注info.flags二进制第三位即可:为0即上面判断为false,为1即上面判断为true。

  • 正数与负数的与运算:负数的异或操作需要先把数转换成补码才行(头不变取反+1)

两个数互为相反数位与操作可有特殊用途,位与所剩恰为最低位。

两个数互为相反数异或可能有特殊用途,异或后,所剩最低位左移一位。

到此我们就将音视频解封装成了音频和视频,并且保存在了指定文件当中,我们分析一下流程:

  1. 构造MediaExtractor(不需要参数) —> 之后的操作使用try/catch包围 —> setDateSource(url)设置本地或者网络资源 —> getTrackConut()获取该资源的通道数 —> for循环通道数 —> 获取某一轨道的媒体格式:getTrackFormat(i)返回一个MediaFormat —> 判断是什么通道根据trackFormat.getString(MediaFormat.KEY_MIME)返回ketMime的startsWith("?") 。
  2. 接下来的操作就确定了音轨和视频轨道,同时确定文件的输出地点。
  3. 构造MediaMuxer (需要指定文件和格式)—> addTrack(trackFormat)添加轨道信息 参数为MediaFormat,注意这里的MediaFormat要是对应的轨道 —> start()开始合成 —> 设置ByteBuffer,用于缓存一帧数据 —> MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo()获取bufferInfo —> 通过mediaExtractor.readSampleData读取数据流,同时也作为一个while循环的判断条件 —> 配置bufferInfo的四个属性 —> 把通过mediaExtractor解封装的数据通过mediaMuxer.writeSampleData写入到对应的轨道 —> 读取下一帧audioExtractor.advance()。
  4. 循环结束之后mediaExtractor.unselectTrack(i)释放选择 —> mediaMuxer.stop()停止 —> 最后释放mediaMuxer和mediaExtractor。

接下来我们开始合成操作

    //把音轨和视频轨再合成新的视频
    private String muxerMp4(String inputAudio , String outPutVideo){
        File videoFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "video.mp4");
        File audioFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), inputAudio);
        File outputFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), outPutVideo);


        if (outputFile.exists()) {
            outputFile.delete();
        }
        if (!videoFile.exists()) {
            Toast.makeText(this, "视频源文件不存在", Toast.LENGTH_SHORT).show();
            return "";
        }
        if (!audioFile.exists()) {
            Toast.makeText(this, "音频源文件不存在", Toast.LENGTH_SHORT).show();
            return "";
        }

        MediaExtractor videoExtractor = new MediaExtractor();
        MediaExtractor audioExtractor = new MediaExtractor();

        try {
            MediaMuxer mediaMuxer = new MediaMuxer(outputFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            int videoTrackIndex = 0;
            int audioTrackIndex = 0;

            //先添加视频轨道
            videoExtractor.setDataSource(videoFile.getAbsolutePath());
            int trackCount = videoExtractor.getTrackCount();

            for (int i = 0; i < trackCount; i++) {
                MediaFormat trackFormat = videoExtractor.getTrackFormat(i);
                String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
                if (TextUtils.isEmpty(mimeType)) {
                    continue;
                }
                if (mimeType.startsWith("video/")) {
                    videoExtractor.selectTrack(i);

                    videoTrackIndex = mediaMuxer.addTrack(trackFormat);
                    break;
                }
            }

            //再添加音频轨道
            audioExtractor.setDataSource(audioFile.getAbsolutePath());
            int trackCountAduio = audioExtractor.getTrackCount();
            for (int i = 0; i < trackCountAduio; i++) {
                MediaFormat trackFormat = audioExtractor.getTrackFormat(i);
                String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
                if (TextUtils.isEmpty(mimeType)) {
                    continue;
                }
                if (mimeType.startsWith("audio/")) {
                    audioExtractor.selectTrack(i);
                    audioTrackIndex = mediaMuxer.addTrack(trackFormat);
                    Log.i(TAG1, "muxerToMp4: audioTrackIndex=" + audioTrackIndex);
                    break;
                }
            }


            //再进行合成
            mediaMuxer.start();
            ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int sampleSize = 0;

            while ((sampleSize = videoExtractor.readSampleData(byteBuffer, 0)) > 0) {

                bufferInfo.flags = videoExtractor.getSampleFlags();
                bufferInfo.offset = 0;
                bufferInfo.size = sampleSize;
                bufferInfo.presentationTimeUs = videoExtractor.getSampleTime();
                mediaMuxer.writeSampleData(videoTrackIndex, byteBuffer, bufferInfo);
                videoExtractor.advance();
            }

            int audioSampleSize = 0;

            MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();


            while ((audioSampleSize = audioExtractor.readSampleData(byteBuffer, 0)) > 0) {

                audioBufferInfo.flags = audioExtractor.getSampleFlags();
                audioBufferInfo.offset = 0;
                audioBufferInfo.size = audioSampleSize;
                audioBufferInfo.presentationTimeUs = audioExtractor.getSampleTime();
                mediaMuxer.writeSampleData(audioTrackIndex, byteBuffer, audioBufferInfo);
                audioExtractor.advance();
            }

            //最后释放资源
            videoExtractor.release();
            audioExtractor.release();
            mediaMuxer.stop();
            mediaMuxer.release();

        } catch (IOException e) {
            e.printStackTrace();
            return "";

        }
        return outputFile.getAbsolutePath();
    }

        因为这个与上面例子的流程大致相同,上面看懂了,下面基本上没什么问题,所以注释相对比较少。至于过程也就懒得分析了。

        我们在解封装的过程中同时使用到了MediaExtractor和MediaMuxer,包括合成的时候也用了这两个。不要想当然的认为MediaExtractor解封装出来两文件,两文件根据MediaMuxer就可以合成!!!

        最后遗留两个问题:

        1.解封装出来的是不同轨道的资源,可是当做文件输出时,除了文件名不同其他的操作都是一模一样,就连mediaMuxer的参数格式都是MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,那音频文件是怎样合成成功的???

        AAC代表Advanced Audio Coding(高级音频编码),是一种由MPEG-4标准定义的有损音频压缩格式。而且解封装出来的音频acc和视频MP4改了后缀都可以正常播放。

        

        音频文件同样可以理解为一帧帧的说法,之后我回使用实时AAC音频帧并通过AudioTrack来播放,尽情期待。

        2.分解出来的轨道是固定的吗?还是根据自定义来的?他的个数只能是一个音频一个视频吗?文章来源地址https://www.toymoban.com/news/detail-429527.html

  • 分解出来的轨道不是固定的但一般是两个轨道(一个音频一个视频)
E/测试Demo: 轨道数量 = 2
E/测试Demo: 0编号通道格式 = video/avc
E/测试Demo: 1编号通道格式 = audio/mp4a-latm
  • 这个具体的顺序就是根据你使用mediaMuxer添加合成的顺序
  • 当然也可能有多个音频和视频在一个盒子里

到了这里,关于Android音视频开发(三)——MediaExtractor和MediaMuxer的使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android音视频开发实战01-环境搭建

    FFmpeg 是一款流行的开源多媒体处理工具,它可以用于转换、编辑、录制和流式传输音视频文件。FFmpeg 具有广泛的应用场景,包括视频编解码、格式转换、裁剪、合并、滤镜等等。官网:https://ffmpeg.org/ FFmpeg 支持各种常见的音视频格式,例如 MP4、AVI、FLV、MOV、AAC、MP3、M4A 等等

    2024年02月10日
    浏览(51)
  • Android音视频开发实战02-Jni

    JNI是Java Native Interface的缩写,是Java提供的一种机制,用于在Java代码中调用本地(C/C++)代码。它允许Java代码与本地代码进行交互,通过JNI,Java应用程序可以调用一些原生库或者操作系统API,以获取更好的性能和更强的功能支持。 使用JNI需要编写一些Native方法,并将其实现在

    2024年02月11日
    浏览(51)
  • Android 音视频开发—MediaPlayer音频与视频的播放介绍

    Android多媒体中的——MediaPlayer,我们可以通过这个API来播放音频和视频该类是Androd多媒体框架中的一个重要组件,通过该类,我们可以以最小的步骤来获取,解码和播放音视频。 它支持三种不同的媒体来源: 本地资源 内部的URI,比如你可以通过ContentResolver来获取 外部URL(流

    2024年02月10日
    浏览(58)
  • 音视频开发系列(6)——全面了解Android MediaFormat

    MediaFormat 是 Android 平台中用于描述音视频格式的类,它提供了许多 API 用于设置和获取音视频的格式信息。以下是 MediaFormat 类的主要 API: 用于创建音频和视频格式的 MediaFormat 对象。需要指定媒体类型(例如 audio/mp4a-latm 或 video/avc)、媒体的采样率、通道数、码率、帧率等信

    2024年02月01日
    浏览(39)
  • Android开源计划-一周开发app,webrtc音视频开发

    题目 – 一周开发app计划 首批参与成员 -小巫 -墨香 -梦痕 -边城刀客 -徐cc 要求 – -每位认领者按照开源规范来做,代码规范和Android开发规范 -每位认领者必须拥有github账号,熟练使用git对代码进来管理 -每个人认领一个功能点或模块 -提出完善的解决方案并提供封装良好的库

    2024年04月08日
    浏览(54)
  • Android 音视频开发实践系列-06-初步了解H.264视频编解码技术标准

    本文来自笔者本人的语雀博客,由于语雀升级后不再满足笔者的需求,因此之后笔者会陆续将一些之前已经发布但尚有价值的文章搬家到CSDN。 作为音视频行业从业者,怎么能不理解H.264视频编解码技术标准?本篇文章主要记录笔者学习过程中对众多优秀博客内容的摘抄整理,

    2023年04月09日
    浏览(51)
  • 【学习】从零开发的Android音视频开发(13)——MediaCodec到OMX框架过程及其硬解码

    在讲NuPlayer时,NuPlayer解码部分会创建MediaCodec,并且最终到达OMX框架,先看MediaCodec的 init 函数 从init函数中可以看到,首先创建了 ACodec ,并且初始化了 ALooper 、 AMessage ,由于ACodec继承自 AHandler ,那么一套消息机制就有了。最后发送 kWhatInit 消息,收到消息的逻辑位于ACodec.

    2023年04月08日
    浏览(48)
  • 音视频开发之旅——音频基础概念、交叉编译原理和实践(LAME的交叉编译)(Android)

    本文章已授权微信公众号郭霖(guolin_blog)转载。 本文主要讲解的是 音频基础概念 、 交叉编译原理和实践(LAME的交叉编译) ,是基于 Android平台 ,示例代码如下所示: AndroidAudioDemo 另外, iOS平台 也有相关的文章,如下所示: 音视频开发之旅——音频基础概念、交叉编译

    2024年04月25日
    浏览(82)
  • Android开发音视频方向学习路线及资源分享,学完还怕什么互联网寒冬?

    好了,回归正题。 光看大纲,大家都知道要学习音视频录制,编码,处理,但是具体不知道怎么做,也不知道怎么入门。我自己在入门的时候也一样,靠着搜索引擎自己一点一点的积累,在这里当然要谢谢在该领域无私奉献的大佬们。所以在这里,我会对知识进行细化,运用

    2024年04月11日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包