精华内容
下载资源
问答
  • 解析MP3信息

    2012-08-01 20:35:40
    MP3的基本歌曲信息存在了MP3文件的最后128个字节里, 其结构是: 1-3 TAG (标签) 4-33 歌曲名(Take Me To Your Heart ) 34-63 歌手...请写一段读MP3歌曲信息程序,要求信息存放在结构成员变量 中,最后打印出来。
  • MP3信息解析源码

    2012-08-27 22:00:58
    MP3的基本歌曲信息存在了MP3文件的最后128个字节里, 其结构是: 1-3 TAG (标签) 4-33 歌曲名(Take Me To Your Heart ) ...• 请写一段读MP3歌曲信息程序,要求信息存放在结构成员变量 中,最后打印出来。
  • QT写的一个对MP3文件ID3信息进行解析的一个程序,底层使用我另外上传一个C文件包
  • MP3文件解析并解码转换成WAV文件的程序,对绝大多数MP3解析正常,提供给大家研究,偶尔会发现有的MP3文件,转换成WAV格式后,声音中间有的地方会出现轻微的吱吱声
  • MP3文件格式解析

    2011-12-27 10:54:28
    MP3文件格式解析,对MP3文件中内容进行介绍,主要应用在解析MP3中歌曲名,专辑名,曲风等信息的文档
  • ID3解析程序,支持mp3,wma,ogg,flac,ape等

    热门讨论 2011-09-03 12:00:26
    ID3解析程序,支持mp3,wma,ogg,flac,ape等. 入口文件:ID3Manage.cpp
  • MP3格式解析

    2012-07-28 17:39:09
    解析MP3音频格式 一、概述 MP3 文件是由帧(frame)构成的,帧是MP3 文件最小的组成单位。MP3 的全称应为MPEG1 Layer-3 音频文件,MPEG(Moving Picture Experts Group)在汉语中译为活动图像专家组,特指活动影音压缩...
  • 前文提到解析MP3标签,程序源码中也已经出现了调用解析MP3标签、打印MP3文件信息的功能,这儿先说说MP3文件信息的解析。  解析MP3的文件信息对MP3解码器来说只是一个附加功能,如果不加入这部分源码,同时删除掉前...
         前文提到解析MP3标签,程序源码中也已经出现了调用解析MP3标签、打印MP3文件信息的功能,这儿先说说MP3文件信息的解析。 
    

          解析MP3的文件信息对MP3解码器来说只是一个附加功能,如果不加入这部分源码,同时删除掉前文源码中的相关调用,不影响解码播放。如果你想编写“迷你”型的MP3解码器,可以忽略这些附加的功能。

          MP3的标签信息位于文件开始处或结尾处,用于表达MP3文件的相关信息,常见的有ID3、APE等。


          ID3 V1 位于文件最后的128字节,如果读取的是网络文件而服务器又不支持随机读取的话,意味着不对对其解析这部分信息。这128字节共表示7个信息:

    [0..2]       3  bytes: ID3 v1标识 -- 'TAG'
    [3..32]     30 bytes: 标题
    [33..62]   30 bytes: 艺术家
    [63..92]   30 bytes: 专辑名
    [93..96]   4  bytes: 发行年份
    [97..126] 30 bytes: v1.0 -- 注释/附加/备注信息
             v1.1 -- 前29 bytes注释/附加/备注信息,最后1 byte音轨信息

    [127]       1  byte : 流派

          从“标题”开始,每部分内容之间用'\0'(字符串结束标志)或'\20'(空格)隔开。


          ID3 V2 表示的信息更丰富,结构更复杂,位于文件开始处或位于APE标签之后。ID3 V2的详细内容请参见http://www.id3.org/id3v2.3.0

          APE V1 & V2 位于文件开始处或ID3 V2之后。详细内容请参见http://cn.bing.com/reference/semhtml/APE_tag (External links下的链接就是APE V2)。有很多MP3的标签信息很混乱,内容重复。由于APE标签出现并在MP3中大量应用得比ID3晚,MP3文件的“有利”位置都被ID3占用,所以APE标签位于文件中的位置让人捉摸不透,情况很复杂,对网络文件来说,判断APE标签的位置要反复在文件中定位,况且有的服务器根本就不支持随机访问,所以我这儿就放弃了对APE的解析,尽管APE的解析过程并不复杂。


           本文只解析ID3 V1的具有的那几项简单的内容,JAVA的字符集转换很方便,所以解析ID3 V2的代码很简洁。ID3 V2的每一帧都以“Frame ID”开始,例如TT2或TIT2表示“标题”,程序中通过计算ID的哈希值来识别不同的帧。需要指出的是,在解码器读入文件时自动对标签信息进行解析,调用IRandomAccess接口的tagAvailable()方法查询是否已经完成对tag的解析完毕,对网络文件,是开线程以后台方式解析。如果对其解析完毕,调用getTitle()等方法就可以返回其内容,如果MP3文件本身没有标签信息,返回值为null。具体调用方法见http://jmp123.sf.net/ 下的API文档。

     

    ID3Tag.java源码:

    /*
    * ID3Tag.java -- 解析MP3文件的ID3 v1/v2 tag
    * Copyright (C) 2010
    *
    * This program is free software: you can redistribute it and/or modify
    * it under the terms of the GNU General Public License as published by
    * the Free Software Foundation, either version 3 of the License, or
    * (at your option) any later version.
    *
    * This program is distributed in the hope that it will be useful,
    * but WITHOUT ANY WARRANTY; without even the implied warranty of
    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    * GNU General Public License for more details.
    *
    * You should have received a copy of the GNU General Public License
    * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    *
    * If you would like to negotiate alternate licensing terms, you may do
    * so by contacting the author: <http://jmp123.sourceforge.net/>
    */
    package jmp123.tag;
    
    import java.io.UnsupportedEncodingException;
    
    /*
     ID3v1:
     [0-2]    3  bytes: ID3 v1标识 -- 'TAG'
     [3—32]   30 bytes: 标题
     [33—62]  30 bytes: 艺术家
     [63—92]  30 bytes: 专辑名
     [93—96]  4  bytes: 发行年份
     [97—126] 30 bytes: v1.0 -- 注释/附加/备注信息
     					v1.1 -- 前29 bytes注释/附加/备注信息,最后1 byte音轨信息
     [127]    1  byte : 流派
    */
    
    public final class ID3Tag {
    	// ID3v1 & ID3v2
    	private String strTitle;
    	private String strArtist;
    	private String strAlbum;
    	private String strYear;
    
    	// ID3v2
    	//private String strLyrics;		// (内嵌)歌词
    	private int intVersion;
    	private int intExHeaderSize;
    	private boolean boolID3v2Footer;
    	//TEXT_ENCODING[0]应由 "ISO-8859-1" 改为 "GBK". ??
    	private static String[] TEXT_ENCODING = {"GBK", "UTF-16", "UTF-16BE", "UTF-8"};
    
    	//--------------------------------------------------------------------
    	// ID3v1 & ID3v2
    
    	public void printTag() {
    		//if (strLyrics != null)
    		//	System.out.println("\r" + strLyrics + "\n");
    		if (strTitle != null)
    			System.out.println("\r        标题: " + strTitle);
    		if (strArtist != null)
    			System.out.println("\r      艺术家: " + strArtist);
    		if (strAlbum != null)
    			System.out.println("\r      唱片集: " + strAlbum);
    		if (strYear != null)
    			System.out.println("\r      发行年: " + strYear);
    	}
    
    	public void destroy() {
    		strTitle = strArtist = strAlbum = strYear = null;
    		//strLyrics = null;
    		intVersion = intExHeaderSize = 0;
    		boolID3v2Footer = false;
    	}
    
    	public String getTitle() {
    		return strTitle;
    	}
    
    	public String getArtist() {
    		return strArtist;
    	}
    
    	public String getAlbum() {
    		return strAlbum;
    	}
    
    	public String getYear() {
    		return strYear;
    	}
    
    	//--------------------------------------------------------------------
    	// ID3v1
    
    	public boolean checkID3V1(byte[] b) {
    		return b[0] == 'T' && b[1] == 'A' && b[2] == 'G';
    	}
    
    	public void parseID3V1(byte[] b) {
    		int i;
    		if (b.length < 128 || checkID3V1(b) == false)
    			return;
    
    		byte[] buf = new byte[125];
    		System.arraycopy(b, 3, buf, 0, 125);
    
    		for (i = 0; i < 30 && buf[i] != 0; i++);
    		if (strTitle == null)
    			strTitle = new String(buf, 0, i).trim();
    		if (strTitle.length() == 0)
    			strTitle = null;
    
    		for (i = 30; i < 60 && buf[i] != 0; i++);
    		if (strArtist == null)
    			strArtist = new String(buf, 30, i-30).trim();
    		if (strArtist.length() == 0)
    			strArtist = null;
    
    		for (i = 60; i < 90 && buf[i] != 0; i++);
    		if (strAlbum == null)
    			strAlbum = new String(buf, 60, i-60).trim();
    		if (strAlbum.length() == 0)
    			strAlbum = null;
    
    		for (i = 90; i < 94 && buf[i] != 0; i++);
    		if (strYear == null)
    			strYear = new String(buf, 90, i-90).trim();
    		if (strYear.length() == 0)
    			strYear = null;
    
    		buf = null;
    	}
    
    	//--------------------------------------------------------------------
    	// ID3v2
    
    	public int checkID3V2(byte[] b, int off) {
    		if(b.length - off < 10)
    			return 0;
    		if(b[off] != 'I' || b[off+1] != 'D' || b[off+2] != '3')
    			return 0;
    
    		intVersion = b[off+3] & 0xff;
    
    		if(intVersion > 2 && (b[off+5] & 0x40) != 0)
    			intExHeaderSize = 1;		//设置为1表示有扩展头
    
    		boolID3v2Footer = (b[off+5] & 0x10) != 0;
    		int size = synchSafeInt(b, off+6);
    		size += 10;					// ID3 header:10bytes 
    		return size;
    	}
    
    	//b[off..]不含ID3v2 头(10 bytes)
    	public void parseID3V2(byte[] b, int off) {
    		int max_size = b.length;
    		int pos = off;
    		if(intExHeaderSize == 1) {
    			intExHeaderSize = synchSafeInt(b, off);
    			pos += intExHeaderSize;
    		}
    		max_size -= 10;		//1 frame header: 10 bytes
    		if(boolID3v2Footer)
    			max_size -= 10;
    
    		//System.out.println("ID3 v2." + intVersion);
    		while(pos < max_size)
    			pos += getText(b, pos, max_size);
    	}
    
    	public static int synchSafeInt(byte[] b, int off) {
    		int i = (b[off] & 0x7f) << 21;
    		i |= (b[off+1] & 0x7f) << 14;
    		i |= (b[off+2] & 0x7f) << 7;
    		i |=  b[off+3] & 0x7f;
    		return i;
    	}
    
    	private int makeInt(byte[] b, int off, int len) {
    		int i, ret = b[off] & 0xff;
    		for (i = 1; i < len; i++) {
    			ret <<= 8;
    			ret |= b[off + i] & 0xff;
    		}
    		return ret;
    	}
    
    	private int getText(byte[] b, int off, int max_size)  {
    		int id_part = 4, frame_header = 10;
    		if(intVersion == 2) {
    			id_part = 3;
    			frame_header = 6;
    		}
    		String id = new String(b, off, id_part);
    		off += id_part;
    
    		int fsize, len;
    		fsize = len = makeInt(b, off, id_part);
    		off += id_part;		// frame size = frame id bytes
    		if (intVersion > 2)
    			off += 2;		// flag: 2 bytes
    
    		int enc = b[off];
    		len--;				// Text encoding: 1 byte
    		off++;				// Text encoding: 1 byte
    		if (len <= 0 || off + len > max_size || enc < 0 || enc >= TEXT_ENCODING.length)
    			return fsize + frame_header;
    		//System.out.println(len+" ------------------------------------ off = " + off);
    		//System.out.println("ID: " + id + ", id.hashCode()=" + id.hashCode());
    		//System.out.println("text encoding: " + TEXT_ENCODING[enc]);
    		//System.out.println("frame size: " + fsize);
    
    		try {
    			switch(id.hashCode()) {
    			case 83378:		//TT2 v2.2
    			case 2575251:	//TIT2  标题
    				if (strTitle == null)
    					strTitle = new String(b, off, len, TEXT_ENCODING[enc]).trim();
    				break;
    			case 83552:
    			case 2590194:	//TYER  发行年
    				if (strYear == null)
    					strYear = new String(b, off, len, TEXT_ENCODING[enc]).trim();
    				break;
    			case 2569358:	//TCON  流派
    				break;
    			case 82815:
    			case 2567331:	//TALB  唱片集
    				if (strAlbum == null)
    					strAlbum = new String(b, off, len, TEXT_ENCODING[enc]).trim();
    				break;
    			case 83253:
    			case 2581512:	//TPE1  艺术家
    				if (strArtist == null)
    					strArtist = new String(b, off, len, TEXT_ENCODING[enc]).trim();
    				break;
    			case 2583398:	//TRCK  音轨
    				break;
    			/*case 2614438:	//USLT  歌词
    				off += 4;	//Languge: 4 bytes
    				len -= 4;
    				strLyrics = new String(b, off, len, TEXT_ENCODING[enc]);
    				break;*/
    			}
    		} catch (UnsupportedEncodingException e) {
    			return fsize + frame_header;
    		} finally {
    			id = null;
    		}
    		return fsize + frame_header;
    	}
    }


     

    展开全文
  • MP3播放程序程序

    2008-09-07 00:39:45
    1)MP3播放 2)频谱显示,显示效果仿Windows的MediaPlay 3)ID3Tag解析程序含完整源程序,因为时间很紧(两个星期),而且仅仅是为了验证基本算法,所以程序并未最终完成; 环境:VC++2005 详细见Readme.txt
  • 易语言虾米网站MP3地址解析源码系统结构:开始读取解析,加入数组_取中间,获取歌曲信息, ======窗口程序集1 || ||------__启动窗口_创建完毕 || ||------_按钮1_被单击 || || ======虾米地址解析
  • mp3 m4e 文件解析程序 源码 mp3文件按帧解析 以及MP3文件头中的信息等 m4e文件按帧解析
  • MP3解码程序源码包

    2010-12-02 16:56:30
    MP3解码程序源码包--MP3解析源代码!!
  • 程序调用vdisk(威盘)上传接口,再将vdisk那边返回的文件信息进行解析 获得解析地址,可实现在线播放以及调用到QQ空间背景音乐播放,免去了开绿钻的烦恼。 程序目前支持mp3、wma格式上传,其他格式上传不了,如...
  • 目前本文中提到的API已支持微信小程序录音文件格式:silk v3、mp3。 注:微信小程序开发工具上的录音不论是新接口还是老接口,都是webm/base64格式,虽然后辍名是silk(或mp3),但不是真正的silk v3(或mp3)格式的...
  • 程序来自网络,仅供技术交流.mp3播放器 音乐播放器 mp3频谱解析器 音频频谱解析器 c++
  • MP3歌词文件解析算法

    2011-04-14 11:04:02
    MP3歌词文件解析 LRC文件算法与排序 代码主体部分用来从lrc数据的开头开始解析时间标签,检测到一个标签就将对应的歌词提取出来,并放入一个链表中。 采用链表是为了方便排序。在这段代码中同时提供了典型的链表排序...
  • 程序调用vdisk(威盘)上传接口,再将vdisk那边返回的文件信息进行解析获得解析地址,可实现在线播放以及调用到QQ空间背景音乐播放,免去了开绿钻的烦恼。程序目前支持mp3、wma格式上传,其他格式上传不了,如需...
  • MP3解码程序(C++)

    2011-11-28 09:38:51
    MP3解码C++程序.rar
  • arm9 mp3解析播放

    2009-12-11 14:39:46
    简易的程序,在mini2440 arm9开发板上播放mp3
  • 用MFC编写的MP3程序支持歌词显示,支持歌曲列表,支持关联文件
  • 直接点击即可使用,如果要知道具体代码,解压jar文件即可
  • esp32~mp3播放实例解析

    千次阅读 2019-03-26 10:36:27
    MP3解码器和I2S流两个元素添加进管道,解码器的输入是MP3文件数据流,I2S流将解码后的音频数据输出到片外,各应用程序之间通过事件接口通信。 1. 代码分析 static const char * TAG = "PLAY_MP3_...

    前言

    esp32-ADF音频框架,Demo看了好几次,也只知道个大概。音频涉及的东西太多了,入门就先熟悉这个框架。
    官方文档:https://docs.espressif.com/projects/esp-adf/en/latest/api-reference/index.html

    Elements of the Audio Development Framework

    框架中包含流、编解码器、音频处理相关的方法和概念。

    Sample Organization of Elements in Audio Pipeline
    将MP3解码器和I2S流两个元素添加进管道,解码器的输入是MP3文件数据流,I2S流将解码后的音频数据输出到片外,各应用程序之间通过事件接口通信。

    1. 代码分析

    static const char *TAG = "PLAY_MP3_FLASH";
    
    /*
       To embed it in the app binary, the mp3 file is named
       in the component.mk COMPONENT_EMBED_TXTFILES variable.
    */
    extern const uint8_t adf_music_mp3_start[] asm("_binary_adf_music_mp3_start");
    extern const uint8_t adf_music_mp3_end[]   asm("_binary_adf_music_mp3_end");
    static int adf_music_mp3_pos;
    
    int mp3_music_read_cb(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx)
    {
        int read_size = adf_music_mp3_end - adf_music_mp3_start - adf_music_mp3_pos;
        if (read_size == 0) {
            return AEL_IO_DONE;
        } else if (len < read_size) {
            read_size = len;
        }
        memcpy(buf, adf_music_mp3_start + adf_music_mp3_pos, read_size);
        adf_music_mp3_pos += read_size;
        return read_size;
    }
    
    void app_main(void)
    {
        audio_pipeline_handle_t pipeline;   // 管道
        audio_element_handle_t i2s_stream_writer, mp3_decoder; // 音频元素
    
        esp_log_level_set("*", ESP_LOG_WARN);
        esp_log_level_set(TAG, ESP_LOG_INFO);
    
        ESP_LOGI(TAG, "[ 1 ] Start audio codec chip");
        audio_hal_codec_config_t audio_hal_codec_cfg =  AUDIO_HAL_ES8388_DEFAULT();
        audio_hal_handle_t hal = audio_hal_init(&audio_hal_codec_cfg, 0); // 初始化编解码驱动
        audio_hal_ctrl_codec(hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START); // 启动解码
        int player_volume;
        audio_hal_get_volume(hal, &player_volume);  // 获取音量
    
        ESP_LOGI(TAG, "[ 2 ] Create audio pipeline, add all elements to pipeline, and subscribe pipeline event");
        audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
        pipeline = audio_pipeline_init(&pipeline_cfg);  // 初始化管道
        mem_assert(pipeline);
    
        ESP_LOGI(TAG, "[2.1] Create mp3 decoder to decode mp3 file and set custom read callback");
        mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
        mp3_decoder = mp3_decoder_init(&mp3_cfg);   // 初始化mp3 decoder元素
        audio_element_set_read_cb(mp3_decoder, mp3_music_read_cb, NULL);
    
        ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
        i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
        i2s_cfg.type = AUDIO_STREAM_WRITER;
        i2s_stream_writer = i2s_stream_init(&i2s_cfg); // 初始化i2s stream元素
    
        ESP_LOGI(TAG, "[2.3] Register all elements to audio pipeline"); // 注册元素到管道中去
        audio_pipeline_register(pipeline, mp3_decoder, "mp3");
        audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");
    
        ESP_LOGI(TAG, "[2.4] Link it together [mp3_music_read_cb]-->mp3_decoder-->i2s_stream-->[codec_chip]");
        audio_pipeline_link(pipeline, (const char *[]) {"mp3", "i2s"}, 2); // 将mp3_music_read_cb mp3_decoder i2s_stream codec_chip关联在一起
    
        ESP_LOGI(TAG, "[ 3 ] Initialize peripherals");
        esp_periph_config_t periph_cfg = { 0 };
        esp_periph_init(&periph_cfg);
    
        ESP_LOGI(TAG, "[3.1] Initialize Touch peripheral");
        periph_touch_cfg_t touch_cfg = {
            .touch_mask = TOUCH_SEL_SET | TOUCH_SEL_PLAY | TOUCH_SEL_VOLUP | TOUCH_SEL_VOLDWN,
            .tap_threshold_percent = 70,
        };
        esp_periph_handle_t touch_periph = periph_touch_init(&touch_cfg);   // 初始化按键
    
        ESP_LOGI(TAG, "[3.2] Start all peripherals");
        esp_periph_start(touch_periph);
    
        ESP_LOGI(TAG, "[ 4 ] Setup event listener");
        audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
        audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);
    
        ESP_LOGI(TAG, "[4.1] Listening event from all elements of pipeline");
        audio_pipeline_set_listener(pipeline, evt);     // 设置按键事件
    
        ESP_LOGI(TAG, "[4.2] Listening event from peripherals");    // 设置外设监听事件
        audio_event_iface_set_listener(esp_periph_get_event_iface(), evt);
    
        ESP_LOGW(TAG, "[ 5 ] Tap touch buttons to control music player:");
        ESP_LOGW(TAG, "      [Play] to start, pause and resume, [Set] to stop.");
        ESP_LOGW(TAG, "      [Vol-] or [Vol+] to adjust volume.");
    
        while (1) {
            audio_event_iface_msg_t msg;
            esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
            if (ret != ESP_OK) {
                ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
                continue;
            }
    
            if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) mp3_decoder
                && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
                audio_element_info_t music_info = {0};
                audio_element_getinfo(mp3_decoder, &music_info);
    
                ESP_LOGI(TAG, "[ * ] Receive music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
                         music_info.sample_rates, music_info.bits, music_info.channels);
    
                audio_element_setinfo(i2s_stream_writer, &music_info);
                i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
                continue;
            }
    
            if (msg.source_type == PERIPH_ID_TOUCH
                && msg.cmd == PERIPH_TOUCH_TAP
                && msg.source == (void *)touch_periph) {
    
                if ((int) msg.data == TOUCH_PLAY) { // 改变播放状态
                    ESP_LOGI(TAG, "[ * ] [Play] touch tap event");
                    audio_element_state_t el_state = audio_element_get_state(i2s_stream_writer);
                    switch (el_state) {
                        case AEL_STATE_INIT :
                            ESP_LOGI(TAG, "[ * ] Starting audio pipeline");
                            audio_pipeline_run(pipeline);
                            break;
                        case AEL_STATE_RUNNING :
                            ESP_LOGI(TAG, "[ * ] Pausing audio pipeline");
                            audio_pipeline_pause(pipeline);
                            break;
                        case AEL_STATE_PAUSED :
                            ESP_LOGI(TAG, "[ * ] Resuming audio pipeline");
                            audio_pipeline_resume(pipeline);
                            break;
                        case AEL_STATE_FINISHED :
                            ESP_LOGI(TAG, "[ * ] Rewinding audio pipeline");
                            audio_pipeline_stop(pipeline);
                            adf_music_mp3_pos = 0;
                            audio_pipeline_resume(pipeline);
                            break;
                        default :
                            ESP_LOGI(TAG, "[ * ] Not supported state %d", el_state);
                    }
                } else if ((int) msg.data == TOUCH_SET) {
                    ESP_LOGI(TAG, "[ * ] [Set] touch tap event");
                    ESP_LOGI(TAG, "[ * ] Stopping audio pipeline");
                    break;
                } else if ((int) msg.data == TOUCH_VOLUP) {
                    ESP_LOGI(TAG, "[ * ] [Vol+] touch tap event");
                    player_volume += 10;
                    if (player_volume > 100) {
                        player_volume = 100;
                    }
                    audio_hal_set_volume(hal, player_volume);
                    ESP_LOGI(TAG, "[ * ] Volume set to %d %%", player_volume);
                } else if ((int) msg.data == TOUCH_VOLDWN) {
                    ESP_LOGI(TAG, "[ * ] [Vol-] touch tap event");
                    player_volume -= 10;
                    if (player_volume < 0) {
                        player_volume = 0;
                    }
                    audio_hal_set_volume(hal, player_volume);
                    ESP_LOGI(TAG, "[ * ] Volume set to %d %%", player_volume);
                }
            }
        }
    
        ESP_LOGI(TAG, "[ 6 ] Stop audio_pipeline");
        audio_pipeline_terminate(pipeline);
    
        audio_pipeline_unregister(pipeline, mp3_decoder);
        audio_pipeline_unregister(pipeline, i2s_stream_writer);
    
        /* Terminate the pipeline before removing the listener */
        audio_pipeline_remove_listener(pipeline);
    
        /* Make sure audio_pipeline_remove_listener is called before destroying event_iface */
        audio_event_iface_destroy(evt);
    
        /* Release all resources */
        audio_pipeline_deinit(pipeline);
        audio_element_deinit(i2s_stream_writer);
        audio_element_deinit(mp3_decoder);
    }
    

    2. 思考

    还是觉得音频太难了,这个框架中就涉及到很多东西。
    参考下面作者学习:从play_mp3例程出发理解ESP32-ADF的使用方法

    3. 实例中mp3文件是如何处理的?

    /*
       To embed it in the app binary, the mp3 file is named
       in the component.mk COMPONENT_EMBED_TXTFILES variable.
    */
    extern const uint8_t adf_music_mp3_start[] asm("_binary_adf_music_mp3_start");
    extern const uint8_t adf_music_mp3_end[]   asm("_binary_adf_music_mp3_end");
    

    在熟悉Demo的过程中对mp3文件的处理不是很清楚,查询网上资料也找不到好的答案。便直接提了个issue,挺幸运的,乐鑫的大佬有答复。根据下面的回复,mp3在实例中是编译转化为二进制的过程中就加入了文件的起始标志。这里又遇到内嵌汇编和Makefile了。
    MP3播放实例

    展开全文
  • android 播放器解析MP3文件乱码分析

    千次阅读 2013-03-12 16:02:27
    我们从windows的中文操作系统下,下载下来mp3,Tag正常显示,推进Android机后,却出现乱码,这是为什么呢?  之所以在中文系统中没有特别的问题,是因为通常电脑生成的MP3简体中文ID3标签是ID3V2.3版本,编码是...
       我们从windows的中文操作系统下,下载下来mp3,Tag正常显示,推进Android机后,却出现乱码,这是为什么呢? 
    

         之所以在中文系统中没有特别的问题,是因为通常电脑生成的MP3简体中文ID3标签是ID3V2.3版本,编码是ANSI,GB2312/GBK从双字节上兼容ANSI标准。而在UTF-8/Unicode为标准的系统中如Linux(Ubuntu),Android中就容易出现这种问题。    

         解决办法先明确几个知识点:

         首先明确一点,文件不存在什么编码(归根结底文件都是二进制文件,用ue打开可以看到都是一个个的16进制数),只有文件中的字符才可以说编码。

         unicode和ansi都是字符代码的一种表示形式。

        使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。

        不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

     

    常见编码介绍

    ansi编码

    最初的计算机是又8个晶体管,通过晶体管的开合与排列可以表示数种状态,所以一个字节就定义为8bit,而一个bit只能有0,1开关的表示,2的8次方是256,所以最初的计算机只能表示256种状态。

    人们定义了前32个为状态字符比如翻页,换行,发出(嘟)的声音等,后来人们为了用计算机存储文字,又把空格,英文字母,数字等加进了进来,总共使用了127个,这时候大家把这个存储方案叫做ANSI的ASCII编码[American Standard Code for Information Interchange,美国信息互换标准代码](http://www.dreamdu.com/xhtml/ascii/),这个表存储英文已经没有 问题了,但是127个里并不包含其它欧洲国际的文字,人们又继续扩展ASCII表的内容,加入了一些字符,与一些画表格的符号,直接扩展到255个。

     

     ASCII和Ansi编码
          字符内码(charcter code)指的是用来代表字符的内码.读者在输入和存储文档时都要使用内码,内码分为
          单字节内码 -- Single-Byte character sets (SBCS),可以支持256个字符编码.
          双字节内码 -- Double-Byte character sets)(DBCS),可以支持65000个字符编码.
         前者即为ASCII编码,后者对应ANSI.
         至于简体中文编码GB2312,实际上它是ANSI的一个代码页936

     

    GB2312与GBK

    国人发现只使用ASCII表根本无法表示汉字!怎么办?没有什么能难道我们!于是我们发明了GB2312编码,此编码完全忽略了ASCII表中 127位后面的内容,127位前面的内容保留,如果两个字节同时大于127(7F)就认为这两个字节表示一个汉字,同时像标点、字母也都重新使用两个字节 定义了一遍,这就是我们经常说的 全角,这种方案可以表示6000种文字。

    但是中国的文字太复杂6000个字也不够用,人们开始扩展GB2312,规定只要一个字节大于127,这个字节和后面的字节组合起来就代表一个汉字,这种编码成为GBK,于是又增加了20000多个汉字!

    这样很多国家都开始定义自己的编码了,日本,韩国等。甚至连中国的台湾省都定义了一种编码 BIG5。所以在当时一个程序要想适应多国语言简直要把人郁闷死。

    如果搞过windows编程的人应该知道,win里面有多字节字符集MBCS(multi-byte character set)的说法,而且MBCS包含两种字符类型,单字节字符SBCS(single-byte characters set)和双字节字符(double-byte characters set)DBCS。我们的GBK与GB2312都是DBCS。所以我们在编程时经常遇到一个中文字符等于两个英文字符的事情。BIG5与日本韩国的编码也 都属于DBCS。

    这下清楚了吧,根本没什么ansi文件或gb2312文件,文件打开时会根据操作系统的编码方式(就是安装在操作系统中的编码解析方式)来尝试打开文件,如果安装了中文编码,就把ansi文件当作中文打开,如果日文编码,就当作日文打开。

    UNICODE与UTF-8

    ISO最后提出了UNICODE(Universal Multiple-Octet Coded Character Set,简称 UCS)编码来解决所有的问题。

    UNICODE编码方式规定使用两个字节(16位)表示表示一个字符,算算2的16次方是多少?原来ANSI规定的都扩充为2字节,并且把所有已知的语言都编码进UNICODE。UNICODE可以表示65536个字符。

    这下多国语言程序开始高兴了,使用UNICODE全部搞定!于是微软重现编辑windows内核,完全使用UNICODE编码,搞过win编程的人应该都知道,以A或W结尾的函数,还有灵巧的_T宏吧?

    虽然UNICODE有很多优点,但是缺点也不少,我先总结我知道的两点:

    1,狂占空间,以前一个字节可以表示,现在却要用两个字节了,网络上有80%属于英文字符,这下网络几乎要扩大一倍!

    所以又有人研究出来了UTF-8(Unicode Translation Format - 8)编码,UNICODE转换格式,对于常用字符使用单字节,汉字等使用双字节。8代表每次在网络上传输8位,如果是UTF-16就是每次传输16位。搞 过网络编程的朋友应该知道,字节序(就是字节的排放顺序)分为两种,主机字节序与网络字节序,就是大头(俗称)在前,小头在前的问题,在网络上面传输的流 的字节序很可能是不一致的,于是需要使用一种方法通知接收端,传输流的字节序。有人发明了一种简单的方法,在每个流的开始加上FFEF或EFFF,分别主 机与网络字节序,我们可以使用记事本保存一个UNICODE文件,再使用ue打开看看(HEX方式打开)。所以有时候网页传到网上,在网页最开始的地方会 出现一个字符,这个有时候很令人费解。

    用记事本新建立两个文件存为UNICODE与UNICODE big endian模式,输入梦之都,保存再用ue打开。

    UNICODE

    FF FE A6 68 4B 4E FD 90

    UNICODE big endian

    FE FF 68 A6 4E 4B 90 FD (观察,没两个字节和上面的对比)

    2,UNICODE与GBK等两字节编码完全不兼容,无法找到一种简单的方式转换(只能使用查找表的方式)

    这点我们可以使用记事本新建立两个文件一个ansi的文件,另一个utf8的文件,分别写入梦之都 ,保存。使用ue的hex模式打开我们会看到。

    UNICODE

    FF FE A6 68 4B 4E FD 90

    ansi

    C3 CE D6 AE B6 BC

     

    清楚了这些,那么我想,必须先判断出是什么国家文字的歌曲,再用什么方式去解ANSI标准码 ,最后转换成utf-8显示。概括一句话:让utf-8正确解析双字节的ANSI。

    展开全文
  • MP3解码算法原理解析

    万次阅读 2018-03-13 18:00:19
    一:MP3编解码整体结构介绍 看懵逼了是吧。这里面有很多概念需要一一讲解。 比特流:比特流是一种内容分发协议。它采用高效的软件分发系统和点对点技术共享大体积文件(如一部电影或电视节目),并使每个用户像...

    写在前面

    如果大家对音视频技术感兴趣,可以订阅我的专题 视频播放器和音视频基础知识

    一:MP3编解码整体结构介绍

    MP3解码流程
    看懵逼了是吧。这里面有很多概念需要一一讲解。

    • 比特流:比特流是一种内容分发协议。它采用高效的软件分发系统和点对点技术共享大体积文件(如一部电影或电视节目),并使每个用户像网络重新分配结点那样提供上传服务。(因为没有专业学过这方面的内容,我暂且把它理解为一段数据,里面的内容有时间再探讨)。

      • 同步及差错检查:mp3的数据流的传输和同步工作中都是以帧为单位的。帧是MP3最小的格式单元,它不可以再被分割。每一帧头部包含了当前帧的一些基本信息,其中就包括同步信息。同步信息的组成是包含连续的12比特的‘1’。mp3视频解码工作的第一步就是使解码器和输入数据流同步。在启动解码器之后,可以搜索数据中连续12个比特的‘1’来完成。在取得同步信息之后,帧头部后续的信息是:帧头信息,包括采样率、填充位、比特率等信息。

      • 哈夫曼解码:你可以这样理解,我把不同的数据通过一个表格进行一一对应,用这个对应的码来表示原来的信息,那么出现频率高的数,我用尽可能短的码来表示。出现频率低的数用长一点的码来表示。这样可以减少表示信息的内容量。而且传输过去之后,再按照这个对照码可以进行还原。大概原理就是这个。

      • 逆量化 量化过程的逆过程,想了解这个各位需要学习一下量化的过程。

      • IMDCT:IMDCT是缩写,全称是:Inverse Modified Discrete Cosine Transform(反向修正离散余弦变换)。在MP3中需要使用此算法将输入数据从频域变换到余弦域,对子带滤波进行补偿运算。使用逆向离散余弦变换的公式,对反量化得出的信号进行变换。IMDCT运算公式大概长这样:IMDCT运算公式IMDCT运算过程如下图:IMDCT运算过程
      • 频率反转:对逆向离散余弦变换的输出值中的奇数号子带(0到31号子带中的1,3,5,…,31)中的奇数号样本值(每个子带中的0到17号样本值的1,3,5,…,17号样本值)进行反相处理,用来补偿编码时为提高离散余弦变换效率而进行的频率反转。

    针对以上的概念,如果有不清楚的,可以看我的另外一篇博客:MP3头帧解析,里面有一些和帧头信息相关的知识。

    同步及差错检查包括了头部信息解码模块 在主控模块开始运行后,主控模块将比特流的数据缓冲区交给同步及差错检查模块,此模块包含两个功能,即头信息解码及帧边信息解码,根据它们的信息进行 尺度因子解码及哈夫曼解码,得出的结果经过逆量化,立体声解码,混淆缩减, IMDCT,频率反转,合成多相滤波这几个模块之后,得出左右声道的 PCM 码 流 , 再由主控模块将其放入输出缓冲区输出到声音播放设备(总之很复杂)。

    2、主控模块

    主控模块的主要任务是操作输入输出缓冲区,调用其它各模块协同工作。 其中,输入输出缓冲区均由 DSP 控制模块提供接口。

    输入缓冲区中放的数据为原始 mp3 压缩数据流,DSP 控制模块每次给出大于最大 可能帧长度的一块缓冲区,这块缓冲区与上次解帧完后的数据(必然小于一帧) 连接在一起,构成新的缓冲区。

    输出缓冲区中将存放的数据为解码出来的 PCM 数据,代表了声音的振幅。它由一 块固定长度的缓冲区构成,通过调用 DSP 控制模块的接口函数,得到头指针,在 完成输出缓冲区的填充后,调用中断处理输出至 I2S 接口所连接的音频 ADC 芯片 (立体声音频 DAC 和 DirectDrive 耳机放大器)输出模拟声音。

    3、同步及差错检测

    同步及差错检测模块主要用于找出数据帧在比特流中的位置,并对以此位置开始 的帧头、CRC 校验码及帧边信息进行解码,这些解码的结果用于后继的尺度因子 解码模块和哈夫曼解码模块。Mpeg1 layer 3 的流的主数据格式见下图:

    主数据的组织结构图
    其中 granule0 和 granule1 表示在一帧里面的粒度组 1 和粒度组 2,channel0 和 channel1 表示在一个粒度组里面的两个通道,scalefactor 为尺度因子 quantized value 为量化后的哈夫曼编码值,它分为 big values 大值区和 count1 1 值区

    CRC 校验:表达式为 X16+X15+X2+1

    3.1 帧同步

    帧同步目的在于找出帧头在比特流中的位置,ISO 1172-3 规定,MPEG1 的帧头 为 12 比特的“1111 1111 1111”,且相邻的两个帧头隔有等间距的字节数,这 个字节数可由下式算出:

    N= 144 * 比特率 / 采样率
    

    如果这个式子的结果不是整数,那么就需要用到一个叫填充位的参数,表示间距 为 N +1。

    3.2 头信息解码

    头信息解码目的是找出这一帧的特征信息,如采样率,是否受保护,是否有填充 位等。头信息见下图:帧头信息结构图
    其长度为 4 字节,数据结构如下:

    typedef struct tagHeader {
    unsigned int sync : 11 ; / / 同步信息
    unsigned int version : 2 ; / / 版本
    unsigned int layer : 2 ; / / 层
    unsigned int error2protection : 1 ; / / CRC 校正 
    unsigned int bit2rate2index : 4 ; / / 位率索引
    unsigned int sample2rate2index : 2 ; / / 采样率索引 
    unsigned int padding : 1 ; / / 空白字
    unsigned int extension : 1 ; / / 私有标志 
    unsigned int channel2mode : 2 ; / / 立体声模式 
    unsigned int mode extension : 2 ; / / 保留
    unsigned int copyright : 1 ; / / 版权标志
    unsigned int original : 1 ; / / 原始媒体
    unsigned int emphasis : 2 ; / / 强调方式
    } HEADER
    

    3.3 帧边信息解码

    帧边信息解码的主要目的在于找出解这帧的各个参数,包括主数据开始位置,尺 度因子长度等。帧边信息如下图所示:帧边信息(side_infomation)表

    3.4 main_data_begin

    main_data_begin(主数据开始)是一个偏移值,指出主数据是在同步字之前多少 个字节开始。需要注意的是,1.帧头不一定是一帧的开始,帧头 CRC 校验字和帧 边信息在帧数据中是滑动的。2.这个数值忽略帧头和帧边信息的存在,如果 main_data_begin = 0, 则主数据从帧边信息的下一个字节开始。 参见下图:同步示意图

    3.5 block_type

    block_type 指出如下三种块类型:

    block_type = 0 长块
    block_type = 1 开始块 
    block_type = 3 结束块 
    block_type = 2 短块
    

    在编码过程中进行 IMDCT 变换时,针对不同信号为同时得到较好的时域和频域 分辨率定义了两种不同的块长:长块的块长为 18 个样本,短块的块长为 6 个样 本。这使得长块对于平稳的声音信号可以得到更高的频率分辨率,而短块对跳变 信号可以得到更高的时域分辨率。由于在短块模式下,3 个短块代替 1 个 长 块 , 而短块的大小恰好是一个长块的 1/3,所以 IMDCT 的样本数不受块长的影响。对 于给定的一帧声音信号,IMDCT 可以全部使用长块或全部使用短块,也可以长短 块混合使用。因为低频区的频域分辨率对音质有重大影响,所以在混合块模式下 , IMDCT 对最低频的 2 个子带使用长块,而对其余的 30 个子带使用短块。这样, 既能保证低频区的频域分辨率,又不会牺牲高频区的时域分辨率。长块和短块之 间的切换有一个过程,一般用一个带特殊长转短(即,起始块 block_type = 1) 或短转长(即终止块,block_type = 3)数据窗口的长块来完成这个长短块之间 的切换。因此长块也就是包括正常窗,起始块和终止块数据窗口的数据块;短块 也包含 18 个数据,但是是由 6 个数据独立加窗后在经过连接计算得到的。

    3.6 big_values, count1

    每一个粒度组的频谱都是用不同的哈夫曼表来进行编码的。编码时,把整个从 0 到奈奎斯特频率的频率范围(共 576 个频率线)分成几个区域,然后再用不同的 表编码。划分过程是根据最大的量化值来完成的,它假设较高频率的值有较低的 幅度或者根本不需要编码。从高频开始,一对一对的计算量化值等于“0”的数 目,此数目记为“rzero”。然后 4 个一组地计算绝对值不超过“1”的量化值(也 就是说,其中只可能有-1,0 和+1 共 3 个可能的量化级别)的数目,记为“count1”,在此区域只应用了 4 个哈夫曼编码表。最后,剩下的偶数个值的 对数记为“big values”, 在此区域只应用了 32 个哈夫曼编码表。在此范围 里的最大绝对值限制为 8191。此后,为增强哈夫曼编码性能,进一步划分了频 谱。也就是说,对 big values 的区域(姑且称为大值区)再细化,目的是为了 得到更好的错误顽健性和更好的编码效率。在不同的区域内应用了不同的哈夫曼 编码表。具体使用哪一个表由 table_select 给出。从帧边信息表中可以看到: 当 window_switch_flag == 0 时,只将大值区在细分为 2 个区,此时 region1_count 无意义,此时的 region0_count 的值是标准默认的;但当 window_switch_flag == 1 时再将大值区细分为 3 个区。但是由于 region0_count 和 region1_count 是根据从 576 个频率线划分的, 因此有可能 超出了 big_values *2 的范围,此时以 big_values *2 为准. region0_count 和 region1_count 表示的只是一个索引值,具体频带要根据标准中的缩放因子频带 表来查得.

    参见下图:
    缩放因子、大值区、1 值区和零值区分布图

    3.7 处理流程

    处理流程

    4、缩放因子(scale factor)解码

    缩放因子用于对哈夫曼解码数据进行逆量化的样点重构。根据帧边信息中的 scalefactor_compress 和标准中的对应表格来确定的 slen1 和 slen2 对缩放因 子进行解码,即直接从主数据块中读取缩放因子信息并存入表 scalefac_l[gr][ch][sfb]和 scalefac_s[gr][ch][sfb]中。对第 2 粒度组解码 时,若为长块,则必须考虑尺度因子选择信息。

    4.1 尺度因子带(scalefactor-band)

    在 mpeg layer 3 中 576 条频率线根据人耳的听觉特性被分成多个组,每个组对 应若干个尺度因子,这些组就叫做尺度因子带,每个长窗有 21 个尺度因子带而 每个短窗有 12 个尺度因子带。

    4.2 scfsi

    scfsi(尺度因子选择信息)用于指出是否将粒度组 1 的尺度因子用于粒度组 2。 如果为 0 表示不用,则在比特流中需读取粒度组 2 的尺度因子。

    4.3 处理流程

    缩放因子解码流程图

    5、哈夫曼解码

    哈夫曼编码是一种变长编码,在 mp3 哈夫曼编码中,高频的一串零值不编码,不 超过 1 的下一个区域使用四维哈夫曼编码,其余的大值区域采用二维哈夫曼编 码,而且可选择地分为三个亚区,每个有独立选择的哈夫曼码表。通过每个亚区 单独的自适应码表,增强编码效率,而且同时降低了对传输误码的敏感度。 在程序实现上,哈夫曼表逻辑存储采用了广义表结构,物理存储上使用数组结构 。 查表时,先读入 4bit 数据,以这 4bit 数据作为索引,其指向的元素有两种类型 , 一种是值结构,另一种是链表指针式结构,在链表指针式结构中给出了还需要读 取的 bit 数,及一个偏移值。如果索引指向的是一个值结构,则这个值结构就包 含了要查找的数据。如果索引指向的是一个链表指针式结构,则还需再读取其中 指定的比特数,再把读取出的比特数同偏移值相加,递归的找下去,直到找到值 结构为止。

    5.1 处理流程

    处理流程

    6、逆量化

    6.1 逆量化公式

    逆量化由下面公式算出:
    短窗模式:

    长窗模式:

    其中:

    is[i]由 huffman 编码构造的频率线
    sbgsubblock_gain
    scalefac_multiplier= (scalefac_scale + 1) / 2

    其它值均可在帧边信息中找到。

    7、联合立体声转换

    7.1 强度立体声转换

    在强度立体声模式中,左声道传的是幅值,右声道的 scalefactor 传的是立体声 的位置 is_pos。需要转换的频率线有一个低边界,这个低边界是由右声道的 zero_part 决定的,并且使用右声道的尺度因子来作为 is_pos。

    强度立体声比
    左声道:

    右声道:
    其中 Mi 是 channel[0]的值,Si 是 channel[1]的值

    7.3 处理流程

    强度立体声模式:
    MS_STEREO 因公式单一,较易理解,故流程图略去。

    8、重排序

    重排序的目的在于把哈夫曼解码之后的短块的每个尺度因子带 3 个窗,每个窗 sfbwidth(尺度因子带宽度)个采样的顺序整理成为每个子带三个窗,每个窗六个 采样 xr[sb][window][freq_line]的顺序。

    8.1 处理流程

    重排序处理流程图

    混淆缩减

    对于长块,在进入 IMDCT 之前应当先进行混淆缩减。其算法思想是用蝶形算法进 行相邻块相邻频率线的调整。如图:混淆缩减算法图
    其计算公式如下:

    其中 ci 可由 ISO 1172.3 table B.9 查得

    计算流程如下(pascal 描述):

    For sb = 1 to 32 do
        For i = 0 to 7 do
            Xar[18sb- i -1] = xr[18sb – i - 1]cs[i] – xr[18sb + i]ca[i]
    
            Xar[18sb+i] = xr[18sb +i]cs[i] + xr[18sb -i- 1]ca[i] 
        End for
    End for
    

    10、IMDCT 覆盖叠加

    MDCT 的目的在于进行时域到频域的转换,减少信号的相关性,使得信号的压缩 可以更加高效地完成,而它的反变换 IMDCT 的目的在于将信号还原为没有变换之 前的数值,使频域值向时域值过渡。

    其公式如下:

    在进行了 IMDCT 变换之后,需对频率信号进行加窗、覆盖、叠加。

    10.1 加窗:

    长块:

    开始块:

    结束块:

    短块的每个窗口分别计算:

    10.2 叠加:

    将每一块变换出来的值的前半部分与前一块的后半部分相加,并把后半部分保留 来和下一块的前半部分相加。如下公式:

    resulti = zi + si
    for i = 0 to 17
    si = zi+18 
    for i = 0 to 17
    

    10.3 Szu-Wei Lee 的快速算法

    Szu-Wei Lee 的 IMDCT 快速算法是针对非 2 的 n 次幂个点的 IMDCT 快速算法。他 的主要步骤如下:

    1. 将 N 点 MDCT 化为 N/2 点 DCT-IV
    2. 将 N/2 点 DCT-IV 化为 N/2 点 SDCT-II
    3. 将 N/2 点 SDCT-II 化为 2 个相同的 N/4 点 SDCT-II
    4. 计算 SDCT-II(9 点)

    在本程序中,因为对短块使用这个快速算法并没有带来较大的速度改善,故只对 长块使用此快速算法,相较于直接运算的 648 次乘和 612 次加来,它只用 43 次 乘和 115 次加。

    11、频率反转

    在 IMDCT 之后,进入合成多相滤波之前必须进行频率反转补偿以校正多相滤波器 组的频率反转。方法是将奇数号子带的奇数个采样值乘以-1.

    12、合成多相滤波

    合成多相滤波的目的是将频域信号转化为时域信号。其原理流程如下:
    合成多相滤波算法图
    上图流程可简述如下:
    1. 将从 32 个子带抽来的 32 个 sample 值通过一个矩阵运算算 出 64 个中间值

    1. 将这 64 个中间值放入一个长度为 1024 的 FIFO 缓冲区(这个 缓冲区初始化为 0)。

    2. 从这个缓冲区中每连续的 128 个值中取头尾各 32 个值,合 为 64 个值。完成后组成 512 值的向量 U

    3. 加窗,即将 Ui 与窗口系数 Di 相乘,得到另一 512 值向量 W

    4. 最后将这 512 值向量 W 每连续的 32 个值中顺次取一个值, 一次共取得 512/32 = 16 个值相加。完成后一共取得 32 个 最终的时域信号值。

    Byeong Gi Lee 的 dct 快速算法

    Byeong Gi Lee 的 DCT 快速算法是用于 2 的 n 次幂个点的 dct 快速算法。它用于 N 点的 DCT 时仅需(N/2) * log2N 次乘法和小于 3·*(N/2)*log2 N ) 次加法。 其基本思想是,将 N 个点的 DCT 转化为两个 N/2 个点的 DCT 的和。 进一步分解, 即重复这个过程,减少乘法数量。

    由于向量 Vi 的运算是一个类似于 DCT 的变换,故使用了此快速算法。32 点运算 共使用了 80 次乘法和 80 次加法 119 次减法。

    术语说明

    MPEGMotion Picture Expert Group
    IMDCT反离散余弦变换
    grgranule 粒度组
    chchannel 通道
    展开全文
  • MP3的ID3解析

    千次阅读 2012-10-22 10:16:57
     但ID3并不是MP3标签的ISO国际标准,ID3的各种版本目前只是一个近乎事实上的标准,并没有人强迫播放器或者编码程序必须支持它。 ID3V1大概有两个版本,由于ID3V1.0没有包括曲目序号的定义,所以Michael Mu

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,776
精华内容 7,910
关键字:

解析mp3信息程序