m4a文件裁剪

原创 2016年08月31日 16:32:05
从一个功能说起:基于一个音频文件,截取其中一个片段作为铃声。实现这个功能,基本有两个方案:一是解码后再取相应片段出来编码;二是直接定位后截取出片段并保存成新文件。相比之下,第一个方案有时间上的明显消耗但可以通吃各种音频格式(只要能解码,然后最终编码为固定格式如aac)。这里介绍第二个方案的实现,并且只考虑m4a文件的截取。
第二个方案,概括来说,就是m4a格式的解析跟m4a文件的生成的过程。
m4a文件,实际是mp4文件(苹果公司重新起了一个名字,用来区分带有视频帧的一般的mp4文件),只是一般只存放音频流。所以,解析m4a文件格式就是解析mp4文件格式(对于写文件也同理)。
要截取m4a的片段,有必要先解析文件格式,获取相关信息(比如采样率、声道数、一帧的样本数、总帧数、每一帧的长度、每一帧的偏移等等),所以理解mp4的文件格式是有必要的。
mp4以atom(或者叫box)构成,所有的数据(包括各种信息以及裸的音频数据)都放在atom中。每个atom由三个字段组成:len(整个atom的长度,4Byte)、type(atom的类型,4Byte)、data(atom保存的数据)。atom可以嵌套。atom的类型有很多,但并不是所有类型都要有才能组成一个有效的mp4文件。有几个类型的atom是一定要有的:ftyp(标识文件格式)、stts(每一帧的样本数)、stsz(每一帧的长度)、stsc(帧与chunk的关系表)、mvhd(时长等信息)、mdat(裸数据)、moov等。具体的结构(包括每个atom的含意、每个字段的大小与含意)应该查看网络上的资源(最好能看到atom的字段表格)。
概念方面:track,即轨道(音频或视频)、流;sample,理解为帧(跟样本的概念不同),对于aac来说一帧包括的样本数是固定的(比如都为1024);chunk,即块,是帧的集合。
在实现上,java上,可以使用ringdroid这个开源的项目。ringdroid现在在git上维护,但它在实现上不同于早期的版本,现在是使用解码再编码的方案。可以找回ringdroid早期的版本,里面分出CheapAAC、CheapMP3等分别对不同的格式音频作处理并且是直接截取。
cheapaac的ReadFile完成m4a文件的解析,WriteFile完成新的m4a文件的写成。cheapaac还实现了增益的计算,可以用来显示音频的波形图。
对于截取,有几个信息是很重要的:{帧的长度即字节数}、{帧的偏移量},根据这两个集合就可以截取了。帧的长度(以及总帧数)在解析stsz时确定,帧的偏移在解析mdat时确定。


cheapaac在实现上有两个问题:
1. 截取出来的片段的时长没有重新设置,仍使用原文件的时长。可以在WriteFile里面这样设置片段的时长,但要注意对于使用mediaplayer来播放的情况是不能加以下的代码的,这可能是解码的处理跟ffmpeg等不一致:
// 在写完stco之后,增加:
long time = System.currentTimeMillis() / 1000;
        time += (66 * 365 + 16) * 24 * 60 * 60;  // number of seconds between 1904 and 1970
        byte[] createTime = new byte[4];
        createTime[0] = (byte)((time >> 24) & 0xFF);
        createTime[1] = (byte)((time >> 16) & 0xFF);
        createTime[2] = (byte)((time >> 8) & 0xFF);
        createTime[3] = (byte)(time & 0xFF);
        long numSamples = 1024 * numFrames;
        long durationMS = (numSamples * 1000) / mSampleRate;
        if ((numSamples * 1000) % mSampleRate > 0) {  // round the duration up.
            durationMS++;
        }
        byte[] numSaplesBytes = new byte[] {
                (byte)((numSamples >> 26) & 0XFF),
                (byte)((numSamples >> 16) & 0XFF),
                (byte)((numSamples >> 8) & 0XFF),
                (byte)(numSamples & 0XFF)
        };
        byte[] durationMSBytes = new byte[] {
                (byte)((durationMS >> 26) & 0XFF),
                (byte)((durationMS >> 16) & 0XFF),
                (byte)((durationMS >> 8) & 0XFF),
                (byte)(durationMS & 0XFF)
        };


        int type = kMDHD;
        Atom atom = mAtomMap.get(type);
        if (atom == null) {
            atom = new Atom();
            mAtomMap.put(type, atom);
        }
        atom.data = new byte[] {
                0, // version, 0 or 1
                0, 0, 0,  // flag
                createTime[0], createTime[1], createTime[2], createTime[3],  // creation time.
                createTime[0], createTime[1], createTime[2], createTime[3],  // modification time.
                0, 0, 0x03, (byte)0xE8,  // timescale = 1000 => duration expressed in ms.  1000为单位
                durationMSBytes[0], durationMSBytes[1], durationMSBytes[2], durationMSBytes[3],  // duration in ms.
                0, 0,     // languages
                0, 0      // pre-defined;
        };
        atom.len = atom.data.length + 8;


        type = kMVHD;
        atom = mAtomMap.get(type);
        if (atom == null) {
            atom = new Atom();
            mAtomMap.put(type, atom);
        }
        atom.data = new byte[] {
                0, // version, 0 or 1
                0, 0, 0, // flag
                createTime[0], createTime[1], createTime[2], createTime[3],  // creation time.
                createTime[0], createTime[1], createTime[2], createTime[3],  // modification time.
                0, 0, 0x03, (byte)0xE8,  // timescale = 1000 => duration expressed in ms.  1000为单位
                durationMSBytes[0], durationMSBytes[1], durationMSBytes[2], durationMSBytes[3],  // duration in ms.
                0, 1, 0, 0,  // rate = 1.0
                1, 0,        // volume = 1.0
                0, 0,        // reserved
                0, 0, 0, 0,  // reserved
                0, 0, 0, 0,  // reserved
                0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // unity matrix for video, 36bytes
                0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0x40, 0, 0, 0,
                0, 0, 0, 0,  // pre-defined
                0, 0, 0, 0,  // pre-defined
                0, 0, 0, 0,  // pre-defined
                0, 0, 0, 0,  // pre-defined
                0, 0, 0, 0,  // pre-defined
                0, 0, 0, 0,  // pre-defined
                0, 0, 0, 2   // next track ID, 4bytes
        };
        atom.len = atom.data.length + 8;
2. 对于由neroAacEnc编码出来的m4a文件,parseMdat不能正常解析裸数据,原因是neroAacEnc在裸数据之前多加了8个字节,这8个字节会使得计算出来的每一帧的偏移都不对,导致后继WriteFile时写出来的每一帧的数据都不对。可以考虑跳过8个字节来解决(在判断为nero编码出来的m4a时):
if (mMdatOffset > 0 && mMdatLength > 0) {
            final int neroAACFrom = 570;
            int neroSkip = 0;
            if (mMdatOffset - neroAACFrom > 0) {
                FileInputStream cs = new FileInputStream(mInputFile);
                cs.skip(mMdatOffset - neroAACFrom);
                final int flagSize = 14;
                byte[] buffer = new byte[flagSize];
                cs.read(buffer, 0, flagSize);
                if (buffer[0] == 'N' && buffer[1] == 'e' && buffer[2] == 'r' && buffer[3] == 'o' && buffer[5] == 'A'
                        && buffer[6] == 'A' && buffer[7] == 'C' && buffer[9] == 'c' && buffer[10] == 'o'
                        && buffer[11] == 'd' && buffer[12] == 'e' && buffer[13] == 'c') {
                    neroSkip = 8;
                }
                cs.close();
            }


            stream = new FileInputStream(mInputFile);
            mMdatOffset += neroSkip; // slip 8 Bytes if need
            stream.skip(mMdatOffset);
            mOffset = mMdatOffset;
            parseMdat(stream, mMdatLength);
        } else {
            throw new java.io.IOException("Didn't find mdat");
        }


附带,neroAcc的命令的使用示例:ffmpeg -i "1.mp3" -f wav  - | neroAacEnc -br 32000 -ignorelength -if - -of  "1.m4a"
-br 码率
-lc/-he/-hev2 编码方式,默认可能是he
-if 输入文件
-of 输出文件
-ignorelength 在以其它输出(如ffmpeg)作为输入时使用
另,使用ffmepg也可以转音频格式:
ffmpeg -i "1.mp3" -f mp4 "1.m4a"
ffmpeg -i "1.mp3" -f ipod "1.m4a"



版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

如何区分mp4格式里面mdat中的音频和视频数据

首先在minf里面有个vmhd和smhd,那么vmhd代表视频,smhd代表音频 然后在stsz中 stsz Box 00 00 73 D8  size of stsz,20 73...

安卓上裁剪m4a | 铃声是怎么制作出来的

怎么简单地实现铃声制作?

精选:深入理解 Docker 内部原理及网络配置

网络绝对是任何系统的核心,对于容器而言也是如此。Docker 作为目前最火的轻量级容器技术,有很多令人称道的功能,如 Docker 的镜像管理。然而,Docker的网络一直以来都比较薄弱,所以我们有必要深入了解Docker的网络知识,以满足更高的网络需求。

裁剪YUV文件

在进行YUV文件操作时,经常会遇到需要选取YUV源文件中的一部分帧,这就需要对源文件进行裁剪。实现的原理就是根据帧大小读取源文件,然后保存需要的帧。下面的代码给出实现的主要过程:int cutYUV(...

Android视频裁剪(含裁剪View)

概述: 整合视频裁剪实现、视频裁剪自定义View,两个项目的代码 使用方法: // -- am_video_seekbar.getStartTime() 获取的是自定义View选择的开始裁...

【Android开发经验】设置用户头像并裁剪,仅仅是这么简单?

转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992    在做APP的时候,如果有用户系统功能,那么一般都逃不了这个需求,就是给用户设置头像,而设置头像,又包括...

Android开发技巧——定制仿微信图片裁剪控件

拍照——裁剪,或者是选择图片——裁剪,是我们设置头像或上传图片时经常需要的一组操作。上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现。背景 下面的需求都来自产品。 裁剪图片要像微信那样,拖动和...

文件头部裁剪程序

工作中,时常碰到要删除文件头部的问题,自己写个小程序用于裁剪文件。

文件上传之图片裁剪

前面两章已经介绍了文件上传的进度条与文件切片,本章讲讲图片裁剪。 在实际项目中,会遇到图片固定宽度和高度,作为一个开发者,我们不会要求用户上传的图片尺寸正好符合要求,那么只能用裁剪来解决。 所谓裁剪,...

linux下裁剪可执行文件

Linux下缩小可执行程序 (一) 非使用符号的去除  (2011-05-31 13:12:41) 转载▼ 标签:  function   sections...

裁剪yuv文件的指定帧数

#include #include #include //实现功能:裁剪yuv文件的第902帧到第1115帧 //后面的while中的函数,刚开始一直在读取,虽然读了不写(有待改进) //可以...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)