Commit 11a60adb533eaed6087324e4f7ebd03a22b8a1d1
0 parents
Init
Showing
40 changed files
with
4890 additions
and
0 deletions
Too many changes to show.
To preserve performance only 40 of 78 files are displayed.
.gitignore
0 → 100644
LICENSE
0 → 100644
| 1 | +++ a/LICENSE | |
| 1 | +Copyright [2019] [matrixy] | |
| 2 | + | |
| 3 | +Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 | +you may not use this file except in compliance with the License. | |
| 5 | +You may obtain a copy of the License at | |
| 6 | + | |
| 7 | + http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | + | |
| 9 | +Unless required by applicable law or agreed to in writing, software | |
| 10 | +distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 | +See the License for the specific language governing permissions and | |
| 13 | +limitations under the License. | |
| 0 | 14 | \ No newline at end of file | ... | ... |
README.md
0 → 100644
| 1 | +++ a/README.md | |
| 1 | +## 目录 | |
| 2 | +<ol> | |
| 3 | + <li><a href="#jtt1078-video-server">简介说明</a></li> | |
| 4 | + <li><a href="#分支说明">分支说明</a></li> | |
| 5 | + <li><a href="#项目说明">项目说明</a></li> | |
| 6 | + <li><a href="#准备工具">准备工具</a></li> | |
| 7 | + <li><a href="#测试步骤">测试步骤</a></li> | |
| 8 | + <li><a href="#测试环境">测试环境</a></li> | |
| 9 | + <li><a href="#TODO">TODO</a></li> | |
| 10 | + <li><a href="#致谢">致谢</a></li> | |
| 11 | + <li><a href="#推荐群友项目">推荐群友项目</a></li> | |
| 12 | + <li><a href="#交流讨论">交流讨论</a></li> | |
| 13 | +</ol> | |
| 14 | + | |
| 15 | +<div align="center"><img src="./doc/1078.png" /></div> | |
| 16 | + | |
| 17 | +<hr /> | |
| 18 | + | |
| 19 | +## jtt1078-video-server | |
| 20 | +基于JT/T 1078协议实现的视频转播服务器,当车机服务器端主动下发**音视频实时传输控制**消息(0x9101)后,车载终端连接到此服务器后,发送指定摄像头所采集的视频流,此项目服务器完成音视频数据接收并转码,完成转播的流程,提供各平台的播放支撑。 | |
| 21 | + | |
| 22 | +同时,本项目在配置 **ffmpeg路径** 及 **rtmp url** 后,将同时输出一路到 **RTMP** 服务器上去,为移动端播放提供音视频支持(注意,由于旁路的RTMP流是通过ffmpeg子进程实现,并且有音频转码的过程,所以性能将有很大的下降)。 | |
| 23 | + | |
| 24 | +> 非常感谢 **孤峰赏月/hx([github/jelycom](https://github.com/jelycom))** 提供的mp3音频支持。 | |
| 25 | + | |
| 26 | +## 分支说明 | |
| 27 | +原项目有4个分支不同的实现方式,现将其它分支全部删除,已经用不上了。 | |
| 28 | +配置了ffmpeg和rtmp,可以想办法同时输出到比如HLS等。 | |
| 29 | + | |
| 30 | +> 有其它语言的开发者,可以参考我的“[JTT/1078音视频传输协议开发指南](https://www.hentai.org.cn/article?id=8)”,我所知道的官方文档里的错误或是缺陷以及坑,我全部写了下来,希望对你有帮助。 | |
| 31 | + | |
| 32 | +### 项目说明 | |
| 33 | +本项目接收来自于车载终端发过来的音视频数据,视频直接封装为FLV TAG,音频完成G.711A、G.711U、ADPCMA、G726到PCM的转码,并使用MP3压缩后再封装为FLV TAG。 | |
| 34 | + | |
| 35 | +#### 视频编码支持 | |
| 36 | +目前几乎所有的终端视频,默认的视频编码都是h264,打包成flv也是非常简单的,有个别厂家使用avs,但是我没有碰到过。本项目目前也只支持h264编码的视频。 | |
| 37 | + | |
| 38 | +#### 音频编码支持 | |
| 39 | +|音频编码|支持|备注| | |
| 40 | +|---|---|---| | |
| 41 | +|G.711A|Y|支持| | |
| 42 | +|G.711U|Y|支持| | |
| 43 | +|ADPCMA|Y|支持| | |
| 44 | +|G.726|Y|支持| | |
| 45 | + | |
| 46 | +音频编码太多,也没那么多设备可以测试的,比较常见的就G.711A和ADPCMA这两种,本程序对于不支持的音频,将作 **静音处理** 。 | |
| 47 | + | |
| 48 | +#### 音频编码转码扩展实现 | |
| 49 | +继承并实现`AudioCodec`类的抽象方法,完成任意音频到PCM编码的转码过程,并且补充`AudioCodec.getCodec()`工厂方法即可。`AudioCodec`抽象类原型如下: | |
| 50 | +```java | |
| 51 | +public abstract class AudioCodec | |
| 52 | +{ | |
| 53 | + // 转换至PCM | |
| 54 | + public abstract byte[] toPCM(byte[] data); | |
| 55 | + // 由PCM转为当前编码,可以留空,反正又没有调用 | |
| 56 | + public abstract byte[] fromPCM(byte[] data); | |
| 57 | +} | |
| 58 | +``` | |
| 59 | + | |
| 60 | +### 准备工具 | |
| 61 | +项目里准备了一个测试程序(`src/main/java/cn.org.hentai.jtt1078.test.VideoPushTest.java`),以及一个数据文件(`src/main/resources/tcpdump.bin`),数据文件是通过工具采集的一段几分钟时长的车载终端发送上来的原始消息包,测试程序可以持续不断的、慢慢的发送数据文件里的内容,用来模拟车载终端发送视频流的过程。 | |
| 62 | + | |
| 63 | +另外,新增了 `cn.org.hentai.jtt1078.test.RTPGenerate` 类,用于读取bin文件,并且修改SIM卡号和通道号,创建大量数据文件以便于压力测试。 | |
| 64 | + | |
| 65 | +### 测试步骤 | |
| 66 | +1. 配置好服务器端,修改`app.properties`里的配置项。 | |
| 67 | +2. 直接在IDE里运行`cn.org.hentai.jtt1078.app.VideoServerApp`,或对项目进行打包,执行`mvn package`,执行`java -jar jtt1078-video-server-1.0-SNAPSHOT.jar`来启动服务器端。 | |
| 68 | +3. 运行`VideoPushTest.java`,开始模拟车载终端的视频推送。 | |
| 69 | +4. 开始后,控制台里会输出显示**start publishing: 013800138999-2**的字样 | |
| 70 | +5. 打开浏览器,输入 **http://localhost:3333/test/multimedia#013800138999-2** 后回车 | |
| 71 | +6. 点击网页上的**play video**,开始播放视频 | |
| 72 | + | |
| 73 | +### 测试环境 | |
| 74 | +我在我自己的VPS上搭建了一个1078音视频环境,完全使用了**flv**分支上的代码来创建,各位可以让终端将音视频发送到此服务器或是使用**netcat**等网络工具发送模拟数据来仿真终端,来体验音视频的效果。下面我们说一下通过**netcat**来模拟终端的方法: | |
| 75 | + | |
| 76 | +|标题|说明| | |
| 77 | +|---|---| | |
| 78 | +|1078音视频服务器|185.251.248.4:10780| | |
| 79 | +|实时音视频播放页面|http://1078.hentai.org.cn/test/multimedia#SIM-CHANNEL| | |
| 80 | + | |
| 81 | +1. 首先,本项目的 **/src/main/resources/** 下的 **tcpdump.bin** 即为我抓包存下来的终端音视频数据文件,通过`cat tcpdump.bin | pv -L 40k -q | nc 185.251.248.4 10780`即可以每秒40kBPS的速度,向服务器端持续的发送数据。 | |
| 82 | +2. 在浏览器里打开**http://1078.hentai.org.cn/test/multimedia#SIM-CHANNEL** (注意替换掉后面的SIM和CHANNEL,即终端的SIM卡号,不足12位前面补0,CHANNEL即为通道号),然后点击网页上的**play video**即可。 | |
| 83 | + | |
| 84 | +> 由于我的服务器IP随时可能会发生变化,建设在尝试连接测试服务器前,先通过`ping www.hentai.org.cn`来确定最新的IP。 | |
| 85 | + | |
| 86 | +### 项目文件说明 | |
| 87 | +``` | |
| 88 | + | |
| 89 | + | |
| 90 | +├── doc | |
| 91 | +│ ├── 1078.png(图标) | |
| 92 | +│ └── ffmpeg.png | |
| 93 | +├── LICENSE(开源协议) | |
| 94 | +├── pom.xml | |
| 95 | +├── README.md(项目说明) | |
| 96 | +├── src | |
| 97 | +│ └── main | |
| 98 | +│ ├── java | |
| 99 | +│ │ └── cn | |
| 100 | +│ │ └── org | |
| 101 | +│ │ └── hentai | |
| 102 | +│ │ └── jtt1078 | |
| 103 | +│ │ ├── app | |
| 104 | +│ │ │ └── VideoServerApp.java(主入口程序) | |
| 105 | +│ │ ├── codec | |
| 106 | +│ │ │ ├── ADPCMCodec.java(ADPCM编解码器) | |
| 107 | +│ │ │ ├── AudioCodec.java(音频编解码抽象父类) | |
| 108 | +│ │ │ ├── G711Codec.java(G711A/alaw编解码器) | |
| 109 | +│ │ │ ├── G711UCodec.java(G711U/ulaw编解码器) | |
| 110 | +│ │ │ ├── g726(G726编解码实现) | |
| 111 | +│ │ │ │ ├── G726_16.java | |
| 112 | +│ │ │ │ ├── G726_24.java | |
| 113 | +│ │ │ │ ├── G726_32.java | |
| 114 | +│ │ │ │ ├── G726_40.java | |
| 115 | +│ │ │ │ ├── G726.java | |
| 116 | +│ │ │ │ └── G726State.java | |
| 117 | +│ │ │ ├── G726Codec.java(G726编解码器) | |
| 118 | +│ │ │ ├── MP3Encoder.java(PCM到MP3压缩编码器) | |
| 119 | +│ │ │ └── SilenceCodec.java(静音化解码器) | |
| 120 | +│ │ ├── entity | |
| 121 | +│ │ │ ├── Audio.java | |
| 122 | +│ │ │ ├── MediaEncoding.java | |
| 123 | +│ │ │ ├── Media.java | |
| 124 | +│ │ │ └── Video.java | |
| 125 | +│ │ ├── flv | |
| 126 | +│ │ │ ├── AudioTag.java | |
| 127 | +│ │ │ ├── FlvAudioTagEncoder.java | |
| 128 | +│ │ │ ├── FlvEncoder.java(H264到FLV封装编码器) | |
| 129 | +│ │ │ └── FlvTag.java | |
| 130 | +│ │ ├── http(内置HTTP服务,提供HTTP-CHUNKED传输支持) | |
| 131 | +│ │ │ ├── GeneralResponseWriter.java | |
| 132 | +│ │ │ └── NettyHttpServerHandler.java | |
| 133 | +│ │ ├── publisher | |
| 134 | +│ │ │ ├── Channel.java(一个通道一个Channel实例,Subscriber订阅Channel上的音频与视频) | |
| 135 | +│ │ │ └── PublishManager.java(管理Channel和Subscriber) | |
| 136 | +│ │ ├── server(负责完成1078 RTP消息包的接收和解码) | |
| 137 | +│ │ │ ├── Jtt1078Decoder.java | |
| 138 | +│ │ │ ├── Jtt1078Handler.java | |
| 139 | +│ │ │ ├── Jtt1078MessageDecoder.java | |
| 140 | +│ │ │ └── Session.java | |
| 141 | +│ │ ├── subscriber | |
| 142 | +│ │ │ ├── RTMPPublisher.java(通过ffmpeg子进程将http-flv另外传输一份到RTMP服务器的实现) | |
| 143 | +│ │ │ ├── Subscriber.java(订阅者抽象类定义) | |
| 144 | +│ │ │ └── VideoSubscriber.java(视频订阅者) | |
| 145 | +│ │ ├── test(测试代码) | |
| 146 | +│ │ │ ├── AudioTest.java | |
| 147 | +│ │ │ ├── ChannelTest.java | |
| 148 | +│ │ │ ├── FuckTest.java | |
| 149 | +│ │ │ ├── G711ATest.java | |
| 150 | +│ │ │ ├── MP3Test.java | |
| 151 | +│ │ │ ├── RTPGenerate.java(通过读取原始消息数据文件,创建N个修改了sim卡号的新数据文件,可用于压力测试) | |
| 152 | +│ │ │ ├── VideoPushTest.java | |
| 153 | +│ │ │ ├── VideoServer.java | |
| 154 | +│ │ │ └── WAVTest.java | |
| 155 | +│ │ └── util | |
| 156 | +│ │ ├── ByteBufUtils.java | |
| 157 | +│ │ ├── ByteHolder.java | |
| 158 | +│ │ ├── ByteUtils.java | |
| 159 | +│ │ ├── Configs.java | |
| 160 | +│ │ ├── FileUtils.java | |
| 161 | +│ │ ├── FLVUtils.java | |
| 162 | +│ │ ├── Packet.java | |
| 163 | +│ │ └── WAVUtils.java | |
| 164 | +│ └── resources | |
| 165 | +│ ├── app.properties(主配置文件) | |
| 166 | +│ ├── audio.html | |
| 167 | +│ ├── g726 | |
| 168 | +│ │ ├── in_16.g726 | |
| 169 | +│ │ ├── in_24.g726 | |
| 170 | +│ │ ├── in_32.g726 | |
| 171 | +│ │ └── in_40.g726 | |
| 172 | +│ ├── log4j.properties | |
| 173 | +│ ├── multimedia.html(测试用音视频播放页面) | |
| 174 | +│ ├── tcpdump.bin(测试用数据文件,音频ADPCM含海思头,视频H264) | |
| 175 | +│ ├── nginx_sample.conf(NGINX反向代理样例,解决6路并发问题) | |
| 176 | +│ ├── test.html | |
| 177 | +│ └── video.html | |
| 178 | +``` | |
| 179 | + | |
| 180 | +### 项目打包说明 | |
| 181 | +通过**mvn package**直接打包成jar包,通过`java -jar jtt1078-video-server-1.0-SNAPSHOT.jar`即可运行,最好把**app.properties**和**multimedia.html**一并放在同一个目录下,因为项目会优先读取文件系统中的配置文件信息。而如果没有本地测试的需求,**multimedia.html**可以不要。 | |
| 182 | + | |
| 183 | +### 注意事项 | |
| 184 | +1. 本项目为JT 1078协议的流媒体服务器部分的实现,不包括1078协议的控制消息交互部分,就是在0x9101指令下发后,终端连接到的音视频服务器的实现。 | |
| 185 | +2. 在一般的浏览器里,比如Chrome下,浏览器限制了对于同一个域名的连接最多只能够有6个并发,所以如果要同时播放多路视频,需要准备多个域名或是端口,通过轮循分配的方式,把视频的传输连接,分配到不同的URL上去。 | |
| 186 | + | |
| 187 | +### 致谢 | |
| 188 | +本项目一开始只是个简单的示例项目,在开源、建立QQ交流群后,得到了大批的同道中人的帮助和支持,在此表示谢意。本项目尚未完全完善,非常高兴能够有更多的朋友一起加入进来,一起提出更加闪亮的想法,建设更加强大的视频监控平台! | |
| 189 | + | |
| 190 | +### 致谢名单 | |
| 191 | +非常感谢以下网友的帮助和支持,以及其他默默支持的朋友们! | |
| 192 | +* 不岸不名 | |
| 193 | +* 故事~ | |
| 194 | +* 小黄瓜要吃饭 | |
| 195 | +* yedajiang44.com([github.com/yedajiang44](https://github.com/yedajiang44)) | |
| 196 | +* 幸福一定强 | |
| 197 | +* minigps-基站定位服务 | |
| 198 | +* 慢慢 | |
| 199 | +* power LXC | |
| 200 | +* 奎杜 | |
| 201 | +* 孤峰赏月/hx([github/jelycom](https://github.com/jelycom)) | |
| 202 | +* 洛奇([cuiyaonan](https://gitee.com/cuiyaonan2000)) | |
| 203 | +* tmyam | |
| 204 | + | |
| 205 | +### 推荐群友项目 | |
| 206 | +|项目|URL|作者|说明| | |
| 207 | +|---|---|---|---| | |
| 208 | +|JT1078|https://github.com/yedajiang44/JT1078|SmallChi/[yedajiang44](https://github.com/yedajiang44)|C#,支持音视频,通过websocket传输flv到前端| | |
| 209 | + | |
| 210 | +### 交流讨论 | |
| 211 | +QQ群:808432702,加入我们,群里有热心的同道中人、相关资料、测试数据、代码以及各种方案的先行者等着你。 | |
| 212 | + | |
| 213 | +### 捐助 | |
| 214 | +开源不易,请我抽支芙蓉王吧。 | |
| 215 | + | |
| 216 | +<img src="./doc/donate.png" /> | |
| 0 | 217 | \ No newline at end of file | ... | ... |
doc/1078.png
0 → 100644
6.17 KB
doc/donate.png
0 → 100644
23.6 KB
doc/ffmpeg.png
0 → 100644
15.5 KB
pom.xml
0 → 100644
| 1 | +++ a/pom.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | + | |
| 3 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| 5 | + <modelVersion>4.0.0</modelVersion> | |
| 6 | + | |
| 7 | + <groupId>cn.org.hentai</groupId> | |
| 8 | + <artifactId>jtt1078-video-server</artifactId> | |
| 9 | + <version>1.0-0</version> | |
| 10 | + | |
| 11 | + <name>jtt1078-video-server</name> | |
| 12 | + <url>http://www.hentai.org.cn/</url> | |
| 13 | + | |
| 14 | + <properties> | |
| 15 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
| 16 | + <maven.compiler.source>1.8</maven.compiler.source> | |
| 17 | + <maven.compiler.target>1.8</maven.compiler.target> | |
| 18 | + <!-- logging --> | |
| 19 | + <log4j.version>1.2.12</log4j.version> | |
| 20 | + <slf4j.version>1.7.12</slf4j.version> | |
| 21 | + </properties> | |
| 22 | + | |
| 23 | + <dependencies> | |
| 24 | + <dependency> | |
| 25 | + <groupId>io.netty</groupId> | |
| 26 | + <artifactId>netty-all</artifactId> | |
| 27 | + <version>4.1.42.Final</version> | |
| 28 | + </dependency> | |
| 29 | + <!-- Logging with SLF4J --> | |
| 30 | + <dependency> | |
| 31 | + <groupId>log4j</groupId> | |
| 32 | + <artifactId>log4j</artifactId> | |
| 33 | + <version>${log4j.version}</version> | |
| 34 | + </dependency> | |
| 35 | + <dependency> | |
| 36 | + <groupId>org.slf4j</groupId> | |
| 37 | + <artifactId>slf4j-api</artifactId> | |
| 38 | + <version>${slf4j.version}</version> | |
| 39 | + </dependency> | |
| 40 | + <dependency> | |
| 41 | + <groupId>org.slf4j</groupId> | |
| 42 | + <artifactId>slf4j-log4j12</artifactId> | |
| 43 | + <version>${slf4j.version}</version> | |
| 44 | + </dependency> | |
| 45 | + | |
| 46 | + <dependency> | |
| 47 | + <groupId>net.sf.json-lib</groupId> | |
| 48 | + <artifactId>json-lib</artifactId> | |
| 49 | + <version>2.4</version> | |
| 50 | + <classifier>jdk15</classifier> | |
| 51 | + </dependency> | |
| 52 | + <!--pcm转mp3--> | |
| 53 | + <dependency> | |
| 54 | + <groupId>de.sciss</groupId> | |
| 55 | + <artifactId>jump3r</artifactId> | |
| 56 | + <version>1.0.5</version> | |
| 57 | + </dependency> | |
| 58 | + </dependencies> | |
| 59 | + | |
| 60 | + <build> | |
| 61 | + <plugins> | |
| 62 | + | |
| 63 | + <plugin> | |
| 64 | + <artifactId>maven-assembly-plugin</artifactId> | |
| 65 | + <configuration> | |
| 66 | + <appendAssemblyId>false</appendAssemblyId> | |
| 67 | + <descriptorRefs> | |
| 68 | + <descriptorRef>jar-with-dependencies</descriptorRef> | |
| 69 | + </descriptorRefs> | |
| 70 | + <archive> | |
| 71 | + <manifest> | |
| 72 | + <!-- 此处指定main方法入口的class --> | |
| 73 | + <mainClass>cn.org.hentai.jtt1078.app.VideoServerApp</mainClass> | |
| 74 | + </manifest> | |
| 75 | + </archive> | |
| 76 | + </configuration> | |
| 77 | + <executions> | |
| 78 | + <execution> | |
| 79 | + <id>make-assembly</id> | |
| 80 | + <phase>package</phase> | |
| 81 | + <goals> | |
| 82 | + <goal>assembly</goal> | |
| 83 | + </goals> | |
| 84 | + </execution> | |
| 85 | + </executions> | |
| 86 | + </plugin> | |
| 87 | + | |
| 88 | + </plugins> | |
| 89 | + </build> | |
| 90 | + | |
| 91 | + | |
| 92 | +</project> | ... | ... |
src/main/java/cn/org/hentai/jtt1078/app/VideoServerApp.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/app/VideoServerApp.java | |
| 1 | +package cn.org.hentai.jtt1078.app; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.http.GeneralResponseWriter; | |
| 4 | +import cn.org.hentai.jtt1078.http.NettyHttpServerHandler; | |
| 5 | +import cn.org.hentai.jtt1078.publisher.PublishManager; | |
| 6 | +import cn.org.hentai.jtt1078.server.Jtt1078Handler; | |
| 7 | +import cn.org.hentai.jtt1078.server.Jtt1078MessageDecoder; | |
| 8 | +import cn.org.hentai.jtt1078.server.SessionManager; | |
| 9 | +import cn.org.hentai.jtt1078.util.Configs; | |
| 10 | +import io.netty.bootstrap.ServerBootstrap; | |
| 11 | +import io.netty.channel.*; | |
| 12 | +import io.netty.channel.nio.NioEventLoopGroup; | |
| 13 | +import io.netty.channel.socket.SocketChannel; | |
| 14 | +import io.netty.channel.socket.nio.NioServerSocketChannel; | |
| 15 | +import io.netty.handler.codec.http.HttpObjectAggregator; | |
| 16 | +import io.netty.handler.codec.http.HttpRequestDecoder; | |
| 17 | +import io.netty.handler.codec.http.HttpResponseEncoder; | |
| 18 | +import org.slf4j.Logger; | |
| 19 | +import org.slf4j.LoggerFactory; | |
| 20 | +import sun.misc.Signal; | |
| 21 | +import sun.misc.SignalHandler; | |
| 22 | +import io.netty.handler.timeout.IdleStateHandler; | |
| 23 | +import java.net.InetAddress; | |
| 24 | +import java.util.concurrent.TimeUnit; | |
| 25 | + | |
| 26 | +/** | |
| 27 | + * Created by matrixy on 2019/4/9. | |
| 28 | + */ | |
| 29 | +public class VideoServerApp | |
| 30 | +{ | |
| 31 | + private static Logger logger = LoggerFactory.getLogger(VideoServerApp.class); | |
| 32 | + | |
| 33 | + public static void main(String[] args) throws Exception | |
| 34 | + { | |
| 35 | + Configs.init("/app.properties"); | |
| 36 | + PublishManager.init(); | |
| 37 | + SessionManager.init(); | |
| 38 | + | |
| 39 | + VideoServer videoServer = new VideoServer(); | |
| 40 | + HttpServer httpServer = new HttpServer(); | |
| 41 | + | |
| 42 | + Signal.handle(new Signal("TERM"), new SignalHandler() | |
| 43 | + { | |
| 44 | + @Override | |
| 45 | + public void handle(Signal signal) | |
| 46 | + { | |
| 47 | + videoServer.shutdown(); | |
| 48 | + httpServer.shutdown(); | |
| 49 | + } | |
| 50 | + }); | |
| 51 | + | |
| 52 | + videoServer.start(); | |
| 53 | + httpServer.start(); | |
| 54 | + } | |
| 55 | + | |
| 56 | + static class VideoServer | |
| 57 | + { | |
| 58 | + private static ServerBootstrap serverBootstrap; | |
| 59 | + | |
| 60 | + private static EventLoopGroup bossGroup; | |
| 61 | + private static EventLoopGroup workerGroup; | |
| 62 | + | |
| 63 | + private static void start() throws Exception | |
| 64 | + { | |
| 65 | + serverBootstrap = new ServerBootstrap(); | |
| 66 | + serverBootstrap.option(ChannelOption.SO_BACKLOG, Configs.getInt("server.backlog", 102400)); | |
| 67 | + bossGroup = new NioEventLoopGroup(Configs.getInt("server.worker-count", Runtime.getRuntime().availableProcessors())); | |
| 68 | + workerGroup = new NioEventLoopGroup(); | |
| 69 | + serverBootstrap.group(bossGroup, workerGroup) | |
| 70 | + .channel(NioServerSocketChannel.class) | |
| 71 | + .childHandler(new ChannelInitializer<SocketChannel>() { | |
| 72 | + @Override | |
| 73 | + protected void initChannel(final SocketChannel channel) throws Exception { | |
| 74 | + ChannelPipeline p = channel.pipeline(); | |
| 75 | + // p.addLast(new IdleStateHandler(10,0,0, TimeUnit.SECONDS)); | |
| 76 | + p.addLast(new Jtt1078MessageDecoder()); | |
| 77 | + // p.addLast(new Jtt808MessageEncoder()); | |
| 78 | + // p.addLast(new JTT808Handler()); | |
| 79 | + p.addLast(new Jtt1078Handler()); | |
| 80 | + } | |
| 81 | + }); | |
| 82 | + | |
| 83 | + int port = Configs.getInt("server.port", 1078); | |
| 84 | + Channel ch = serverBootstrap.bind(InetAddress.getByName("0.0.0.0"), port).sync().channel(); | |
| 85 | + logger.info("Video Server started at: {}", port); | |
| 86 | + ch.closeFuture(); | |
| 87 | + } | |
| 88 | + | |
| 89 | + private static void shutdown() | |
| 90 | + { | |
| 91 | + try | |
| 92 | + { | |
| 93 | + bossGroup.shutdownGracefully(); | |
| 94 | + workerGroup.shutdownGracefully(); | |
| 95 | + } | |
| 96 | + catch(Exception e) | |
| 97 | + { | |
| 98 | + e.printStackTrace(); | |
| 99 | + } | |
| 100 | + } | |
| 101 | + } | |
| 102 | + | |
| 103 | + static class HttpServer | |
| 104 | + { | |
| 105 | + private static ServerBootstrap serverBootstrap; | |
| 106 | + | |
| 107 | + private static EventLoopGroup bossGroup; | |
| 108 | + private static EventLoopGroup workerGroup; | |
| 109 | + | |
| 110 | + private static void start() throws Exception | |
| 111 | + { | |
| 112 | + bossGroup = new NioEventLoopGroup(); | |
| 113 | + workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors()); | |
| 114 | + | |
| 115 | + ServerBootstrap bootstrap = new ServerBootstrap(); | |
| 116 | + bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) | |
| 117 | + .childHandler(new ChannelInitializer<SocketChannel>() | |
| 118 | + { | |
| 119 | + @Override | |
| 120 | + public void initChannel(SocketChannel ch) throws Exception | |
| 121 | + { | |
| 122 | + ch.pipeline().addLast( | |
| 123 | + new GeneralResponseWriter(), | |
| 124 | + new HttpResponseEncoder(), | |
| 125 | + new HttpRequestDecoder(), | |
| 126 | + new HttpObjectAggregator(1024 * 64), | |
| 127 | + new NettyHttpServerHandler() | |
| 128 | + ); | |
| 129 | + } | |
| 130 | + }).option(ChannelOption.SO_BACKLOG, 1024) | |
| 131 | + .childOption(ChannelOption.SO_KEEPALIVE, true); | |
| 132 | + try | |
| 133 | + { | |
| 134 | + int port = Configs.getInt("server.http.port", 3333); | |
| 135 | + ChannelFuture f = bootstrap.bind(InetAddress.getByName("0.0.0.0"), port).sync(); | |
| 136 | + logger.info("HTTP Server started at: {}", port); | |
| 137 | + f.channel().closeFuture().sync(); | |
| 138 | + } | |
| 139 | + catch (InterruptedException e) | |
| 140 | + { | |
| 141 | + logger.error("http server error", e); | |
| 142 | + } | |
| 143 | + finally | |
| 144 | + { | |
| 145 | + workerGroup.shutdownGracefully(); | |
| 146 | + bossGroup.shutdownGracefully(); | |
| 147 | + } | |
| 148 | + } | |
| 149 | + | |
| 150 | + private static void shutdown() | |
| 151 | + { | |
| 152 | + try | |
| 153 | + { | |
| 154 | + bossGroup.shutdownGracefully(); | |
| 155 | + workerGroup.shutdownGracefully(); | |
| 156 | + } | |
| 157 | + catch(Exception e) | |
| 158 | + { | |
| 159 | + e.printStackTrace(); | |
| 160 | + } | |
| 161 | + } | |
| 162 | + } | |
| 163 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/ADPCMCodec.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/ADPCMCodec.java | |
| 1 | +package cn.org.hentai.jtt1078.codec; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.server.Jtt1078Decoder; | |
| 4 | +import cn.org.hentai.jtt1078.util.ByteHolder; | |
| 5 | +import cn.org.hentai.jtt1078.util.Packet; | |
| 6 | + | |
| 7 | +import java.io.ByteArrayInputStream; | |
| 8 | +import java.io.ByteArrayOutputStream; | |
| 9 | +import java.io.FileInputStream; | |
| 10 | +import java.io.FileOutputStream; | |
| 11 | +import java.util.Arrays; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * Created by houcheng on 2019-12-05. | |
| 15 | + * ADPCM 和 PCM转换 | |
| 16 | + */ | |
| 17 | +public final class ADPCMCodec extends AudioCodec | |
| 18 | +{ | |
| 19 | + static int[] indexTable = { | |
| 20 | + -1, -1, -1, -1, 2, 4, 6, 8, | |
| 21 | + -1, -1, -1, -1, 2, 4, 6, 8 | |
| 22 | + }; | |
| 23 | + | |
| 24 | + static int[] stepsizeTable = { | |
| 25 | + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, | |
| 26 | + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, | |
| 27 | + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, | |
| 28 | + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, | |
| 29 | + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, | |
| 30 | + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, | |
| 31 | + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, | |
| 32 | + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, | |
| 33 | + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 | |
| 34 | + }; | |
| 35 | + | |
| 36 | + public static class State | |
| 37 | + { | |
| 38 | + public short valprev; | |
| 39 | + public byte index; | |
| 40 | + } | |
| 41 | + | |
| 42 | + public byte[] toPCM(byte[] data) | |
| 43 | + { | |
| 44 | + State state = new State(); | |
| 45 | + int dlen = data.length / 2; | |
| 46 | + byte[] temp; | |
| 47 | + // 如果前四字节是00 01 52 00,则是海思头,需要去掉,否则就视为普通的ADPCM编码 | |
| 48 | + if (data[0] == 0x00 && data[1] == 0x01 && (data[2] & 0xff) == (data.length - 4) / 2 && data[3] == 0x00) | |
| 49 | + { | |
| 50 | + dlen = (data.length - 8); | |
| 51 | + temp = new byte[data.length - 8]; | |
| 52 | + System.arraycopy(data, 8, temp, 0, temp.length); | |
| 53 | + | |
| 54 | + state.valprev = (short)(((data[5] << 8) & 0xff00) | (data[4] & 0xff)); | |
| 55 | + state.index = data[6]; | |
| 56 | + } | |
| 57 | + else | |
| 58 | + { | |
| 59 | + dlen = data.length - 4; | |
| 60 | + temp = new byte[data.length - 4]; | |
| 61 | + System.arraycopy(data, 4, temp, 0, temp.length); | |
| 62 | + | |
| 63 | + state.valprev = (short)(((data[1] << 8) & 0xff00) | (data[0] & 0xff)); | |
| 64 | + state.index = data[2]; | |
| 65 | + } | |
| 66 | + short[] outdata = new short[dlen * 2]; | |
| 67 | + adpcm_decoder(temp, outdata, dlen * 2, state); | |
| 68 | + temp = new byte[dlen * 4]; | |
| 69 | + for (int i = 0, k = 0; i < outdata.length; i++) | |
| 70 | + { | |
| 71 | + short s = outdata[i]; | |
| 72 | + temp[k++] = (byte)(s & 0xff); | |
| 73 | + temp[k++] = (byte)((s >> 8) & 0xff); | |
| 74 | + } | |
| 75 | + return temp; | |
| 76 | + } | |
| 77 | + | |
| 78 | + public byte[] fromPCM(byte[] data) | |
| 79 | + { | |
| 80 | + return null; | |
| 81 | + } | |
| 82 | + | |
| 83 | + public static void adpcm_coder(short[] indata, byte[] outdata, int len, State state) | |
| 84 | + { | |
| 85 | + int val; /* Current input sample value */ | |
| 86 | + int sign; /* Current adpcm sign bit */ | |
| 87 | + int delta; /* Current adpcm output value */ | |
| 88 | + int diff; /* Difference between val and valprev */ | |
| 89 | + int step; /* Stepsize */ | |
| 90 | + int valpred; /* Predicted output value */ | |
| 91 | + int vpdiff; /* Current change to valpred */ | |
| 92 | + int index; /* Current step change index */ | |
| 93 | + int outputbuffer = 0; /* place to keep previous 4-bit value */ | |
| 94 | + int bufferstep; /* toggle between outputbuffer/output */ | |
| 95 | + | |
| 96 | + byte[] outp = outdata; | |
| 97 | + short[] inp = indata; | |
| 98 | + | |
| 99 | + valpred = state.valprev; | |
| 100 | + index = state.index; | |
| 101 | + step = stepsizeTable[index]; | |
| 102 | + | |
| 103 | + bufferstep = 1; | |
| 104 | + | |
| 105 | + int k = 0; | |
| 106 | + for ( int i = 0; len > 0 ; len--, i++) { | |
| 107 | + val = inp[i]; | |
| 108 | + | |
| 109 | + /* Step 1 - compute difference with previous value */ | |
| 110 | + diff = val - valpred; | |
| 111 | + sign = (diff < 0) ? 8 : 0; | |
| 112 | + if ( sign != 0) diff = (-diff); | |
| 113 | + | |
| 114 | + /* Step 2 - Divide and clamp */ | |
| 115 | + /* Note: | |
| 116 | + ** This code *approximately* computes: | |
| 117 | + ** delta = diff*4/step; | |
| 118 | + ** vpdiff = (delta+0.5)*step/4; | |
| 119 | + ** but in shift step bits are dropped. The net result of this is | |
| 120 | + ** that even if you have fast mul/div hardware you cannot put it to | |
| 121 | + ** good use since the fixup would be too expensive. | |
| 122 | + */ | |
| 123 | + delta = 0; | |
| 124 | + vpdiff = (step >> 3); | |
| 125 | + | |
| 126 | + if ( diff >= step ) { | |
| 127 | + delta = 4; | |
| 128 | + diff -= step; | |
| 129 | + vpdiff += step; | |
| 130 | + } | |
| 131 | + step >>= 1; | |
| 132 | + if ( diff >= step ) { | |
| 133 | + delta |= 2; | |
| 134 | + diff -= step; | |
| 135 | + vpdiff += step; | |
| 136 | + } | |
| 137 | + step >>= 1; | |
| 138 | + if ( diff >= step ) { | |
| 139 | + delta |= 1; | |
| 140 | + vpdiff += step; | |
| 141 | + } | |
| 142 | + | |
| 143 | + /* Step 3 - Update previous value */ | |
| 144 | + if ( sign != 0 ) | |
| 145 | + valpred -= vpdiff; | |
| 146 | + else | |
| 147 | + valpred += vpdiff; | |
| 148 | + | |
| 149 | + /* Step 4 - Clamp previous value to 16 bits */ | |
| 150 | + if ( valpred > 32767 ) | |
| 151 | + valpred = 32767; | |
| 152 | + else if ( valpred < -32768 ) | |
| 153 | + valpred = -32768; | |
| 154 | + | |
| 155 | + /* Step 5 - Assemble value, update index and step values */ | |
| 156 | + delta |= sign; | |
| 157 | + | |
| 158 | + index += indexTable[delta]; | |
| 159 | + if ( index < 0 ) index = 0; | |
| 160 | + if ( index > 88 ) index = 88; | |
| 161 | + step = stepsizeTable[index]; | |
| 162 | + | |
| 163 | + /* Step 6 - Output value */ | |
| 164 | + if ( bufferstep != 0 ) { | |
| 165 | + outputbuffer = (delta << 4) & 0xf0; | |
| 166 | + } else { | |
| 167 | + outp[k++] = (byte)((delta & 0x0f) | outputbuffer); | |
| 168 | + } | |
| 169 | + bufferstep = bufferstep == 0 ? 1 : 0; | |
| 170 | + } | |
| 171 | + | |
| 172 | + /* Output last step, if needed */ | |
| 173 | + if ( bufferstep == 0 ) | |
| 174 | + outp[k++] = (byte)outputbuffer; | |
| 175 | + | |
| 176 | + state.valprev = (short)valpred; | |
| 177 | + state.index = (byte)index; | |
| 178 | + } | |
| 179 | + | |
| 180 | + | |
| 181 | + public static void adpcm_decoder(byte[] indata, short[] outdata, int len, State state) | |
| 182 | + { | |
| 183 | + // signed char *inp; /* Input buffer pointer */ | |
| 184 | + // short *outp; /* output buffer pointer */ | |
| 185 | + int sign; /* Current adpcm sign bit */ | |
| 186 | + int delta; /* Current adpcm output value */ | |
| 187 | + int step; /* Stepsize */ | |
| 188 | + int valpred; /* Predicted value */ | |
| 189 | + int vpdiff; /* Current change to valpred */ | |
| 190 | + int index; /* Current step change index */ | |
| 191 | + int inputbuffer = 0; /* place to keep next 4-bit value */ | |
| 192 | + int bufferstep; /* toggle between inputbuffer/input */ | |
| 193 | + | |
| 194 | + short[] outp = outdata; | |
| 195 | + byte[] inp = indata; | |
| 196 | + | |
| 197 | + valpred = state.valprev; | |
| 198 | + index = state.index; | |
| 199 | + if ( index < 0 ) index = 0; | |
| 200 | + if ( index > 88 ) index = 88; | |
| 201 | + step = stepsizeTable[index]; | |
| 202 | + | |
| 203 | + bufferstep = 0; | |
| 204 | + | |
| 205 | + int k = 0; | |
| 206 | + for ( int i = 0; len > 0 ; len-- ) { | |
| 207 | + | |
| 208 | + /* Step 1 - get the delta value */ | |
| 209 | + if ( bufferstep != 0 ) { | |
| 210 | + delta = inputbuffer & 0xf; | |
| 211 | + } else { | |
| 212 | + inputbuffer = inp[i++]; | |
| 213 | + delta = (inputbuffer >> 4) & 0xf; | |
| 214 | + } | |
| 215 | + bufferstep = bufferstep == 0 ? 1 : 0; | |
| 216 | + | |
| 217 | + /* Step 2 - Find new index value (for later) */ | |
| 218 | + index += indexTable[delta]; | |
| 219 | + if ( index < 0 ) index = 0; | |
| 220 | + if ( index > 88 ) index = 88; | |
| 221 | + | |
| 222 | + /* Step 3 - Separate sign and magnitude */ | |
| 223 | + sign = delta & 8; | |
| 224 | + delta = delta & 7; | |
| 225 | + | |
| 226 | + /* Step 4 - Compute difference and new predicted value */ | |
| 227 | + /* | |
| 228 | + ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment | |
| 229 | + ** in adpcm_coder. | |
| 230 | + */ | |
| 231 | + vpdiff = step >> 3; | |
| 232 | + if ( (delta & 4) > 0 ) vpdiff += step; | |
| 233 | + if ( (delta & 2) > 0 ) vpdiff += step>>1; | |
| 234 | + if ( (delta & 1) > 0 ) vpdiff += step>>2; | |
| 235 | + | |
| 236 | + if ( sign != 0 ) | |
| 237 | + valpred -= vpdiff; | |
| 238 | + else | |
| 239 | + valpred += vpdiff; | |
| 240 | + | |
| 241 | + /* Step 5 - clamp output value */ | |
| 242 | + if ( valpred > 32767 ) | |
| 243 | + valpred = 32767; | |
| 244 | + else if ( valpred < -32768 ) | |
| 245 | + valpred = -32768; | |
| 246 | + | |
| 247 | + /* Step 6 - Update step value */ | |
| 248 | + step = stepsizeTable[index]; | |
| 249 | + | |
| 250 | + /* Step 7 - Output value */ | |
| 251 | + outp[k++] = (short)valpred; | |
| 252 | + } | |
| 253 | + | |
| 254 | + state.valprev = (short)valpred; | |
| 255 | + state.index = (byte)index; | |
| 256 | + } | |
| 257 | + | |
| 258 | + | |
| 259 | + public static void main(String[] args) throws Exception | |
| 260 | + { | |
| 261 | + ByteArrayInputStream bais = null; | |
| 262 | + ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 1024 * 4); | |
| 263 | + | |
| 264 | + int len = -1; | |
| 265 | + byte[] block = new byte[512]; | |
| 266 | + FileInputStream fis = new FileInputStream("d:\\test\\g711\\streamax.bin"); | |
| 267 | + FileOutputStream fos = new FileOutputStream("d:\\test\\g711\\111111111122222222222222.pcm"); | |
| 268 | + | |
| 269 | + ADPCMCodec codec = new ADPCMCodec(); | |
| 270 | + | |
| 271 | + Jtt1078Decoder decoder = new Jtt1078Decoder(); | |
| 272 | + while ((len = fis.read(block)) > -1) | |
| 273 | + { | |
| 274 | + decoder.write(block, 0, len); | |
| 275 | + while (true) | |
| 276 | + { | |
| 277 | + Packet p = decoder.decode(); | |
| 278 | + if (p == null) break; | |
| 279 | + | |
| 280 | + int lengthOffset = 28; | |
| 281 | + int dataType = (p.seek(15).nextByte() >> 4) & 0x0f; | |
| 282 | + // 透传数据类型:0100,没有后面的时间以及Last I Frame Interval和Last Frame Interval字段 | |
| 283 | + if (dataType == 0x04) lengthOffset = 28 - 8 - 2 - 2; | |
| 284 | + else if (dataType == 0x03) lengthOffset = 28 - 4; | |
| 285 | + | |
| 286 | + // FFMpegManager.getInstance().feed(publisherId, packet.seek(lengthOffset + 2).nextBytes()); | |
| 287 | + if (dataType == 0x00 || dataType == 0x01 || dataType == 0x02) | |
| 288 | + { | |
| 289 | + | |
| 290 | + } | |
| 291 | + else | |
| 292 | + { | |
| 293 | + byte[] data = p.seek(lengthOffset + 2).nextBytes(); | |
| 294 | + fos.write(codec.toPCM(data)); | |
| 295 | + fos.flush(); | |
| 296 | + } | |
| 297 | + } | |
| 298 | + } | |
| 299 | + | |
| 300 | + fos.flush(); | |
| 301 | + | |
| 302 | + fis.close(); | |
| 303 | + fos.close(); | |
| 304 | + } | |
| 305 | + | |
| 306 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/AudioCodec.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/AudioCodec.java | |
| 1 | +package cn.org.hentai.jtt1078.codec; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.entity.MediaEncoding; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * Created by houcheng on 2019-12-11. | |
| 7 | + */ | |
| 8 | +public abstract class AudioCodec | |
| 9 | +{ | |
| 10 | + public abstract byte[] toPCM(byte[] data); | |
| 11 | + public abstract byte[] fromPCM(byte[] data); | |
| 12 | + | |
| 13 | + public static AudioCodec getCodec(int encoding) | |
| 14 | + { | |
| 15 | + if (MediaEncoding.Encoding.ADPCMA.ordinal() == encoding) return new ADPCMCodec(); | |
| 16 | + else if (MediaEncoding.Encoding.G711A.ordinal() == encoding) return new G711Codec(); | |
| 17 | + else if (MediaEncoding.Encoding.G711U.ordinal() == encoding) return new G711UCodec(); | |
| 18 | + else if (MediaEncoding.Encoding.G726.ordinal() == encoding) return new G726Codec(); | |
| 19 | + // else if (Audio.Encoding.G726.equals(encoding)) ; | |
| 20 | + else return new SilenceCodec(); | |
| 21 | + } | |
| 22 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/G711Codec.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/G711Codec.java | |
| 1 | +package cn.org.hentai.jtt1078.codec; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.codec.AudioCodec; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 核心转换,PCM转G711 | |
| 7 | + * Created by onlygx | |
| 8 | + */ | |
| 9 | +public class G711Codec extends AudioCodec | |
| 10 | +{ | |
| 11 | + private final static int SIGN_BIT = 0x80; | |
| 12 | + private final static int QUANT_MASK = 0xf; | |
| 13 | + private final static int SEG_SHIFT = 4; | |
| 14 | + private final static int SEG_MASK = 0x70; | |
| 15 | + static short[] seg_end = {0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF}; | |
| 16 | + private final static int cClip = 32635; | |
| 17 | + private static final byte[] aLawCompressTable = new byte[]{1, 1, 2, 2, 3, 3, 3, | |
| 18 | + 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, | |
| 19 | + 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, | |
| 20 | + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, | |
| 21 | + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, | |
| 22 | + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, | |
| 23 | + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; | |
| 24 | + | |
| 25 | + private static byte linearToALawSample(short sample) { | |
| 26 | + int sign; | |
| 27 | + int exponent; | |
| 28 | + int mantissa; | |
| 29 | + int s; | |
| 30 | + | |
| 31 | + sign = ((~sample) >> 8) & 0x80; | |
| 32 | + if (!(sign == 0x80)) { | |
| 33 | + sample = (short) -sample; | |
| 34 | + } | |
| 35 | + if (sample > cClip) { | |
| 36 | + sample = cClip; | |
| 37 | + } | |
| 38 | + if (sample >= 256) { | |
| 39 | + exponent = aLawCompressTable[(sample >> 8) & 0x7F]; | |
| 40 | + mantissa = (sample >> (exponent + 3)) & 0x0F; | |
| 41 | + s = (exponent << 4) | mantissa; | |
| 42 | + } else { | |
| 43 | + s = sample >> 4; | |
| 44 | + } | |
| 45 | + s ^= (sign ^ 0x55); | |
| 46 | + return (byte) s; | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * PCM转G711 | |
| 51 | + * | |
| 52 | + * @param src 编码前的数据 | |
| 53 | + * @return 编码后res数组长度应为编码前src数组长度的一半 | |
| 54 | + */ | |
| 55 | + public static byte[] encode(byte[] src) { | |
| 56 | + int j = 0; | |
| 57 | + int len = src.length; | |
| 58 | + int count = len / 2; | |
| 59 | + byte[] res = new byte[count]; | |
| 60 | + short sample = 0; | |
| 61 | + for (int i = 0; i < count; i++) { | |
| 62 | + sample = (short) (((src[j++] & 0xff) | (src[j++]) << 8)); | |
| 63 | + res[i] = linearToALawSample(sample); | |
| 64 | + } | |
| 65 | + return res; | |
| 66 | + } | |
| 67 | + | |
| 68 | + static short search(short val, short[] table, short size) { | |
| 69 | + | |
| 70 | + for (short i = 0; i < size; i++) { | |
| 71 | + if (val <= table[i]) { | |
| 72 | + return i; | |
| 73 | + } | |
| 74 | + } | |
| 75 | + return size; | |
| 76 | + } | |
| 77 | + | |
| 78 | + public static byte linear2alaw(short pcm_val) { | |
| 79 | + short mask; | |
| 80 | + short seg; | |
| 81 | + char aval; | |
| 82 | + if (pcm_val >= 0) { | |
| 83 | + mask = 0xD5; | |
| 84 | + } else { | |
| 85 | + mask = 0x55; | |
| 86 | + pcm_val = (short) (-pcm_val - 1); | |
| 87 | + } | |
| 88 | + | |
| 89 | + /* Convert the scaled magnitude to segment number. */ | |
| 90 | + seg = search(pcm_val, seg_end, (short) 8); | |
| 91 | + | |
| 92 | + /* Combine the sign, segment, and quantization bits. */ | |
| 93 | + | |
| 94 | + if (seg >= 8) /* out of range, return maximum value. */ return (byte) (0x7F ^ mask); | |
| 95 | + else { | |
| 96 | + aval = (char) (seg << SEG_SHIFT); | |
| 97 | + if (seg < 2) aval |= (pcm_val >> 4) & QUANT_MASK; | |
| 98 | + else aval |= (pcm_val >> (seg + 3)) & QUANT_MASK; | |
| 99 | + return (byte) (aval ^ mask); | |
| 100 | + } | |
| 101 | + } | |
| 102 | + | |
| 103 | + | |
| 104 | + public static short alaw2linear(byte a_val) { | |
| 105 | + short t; | |
| 106 | + short seg; | |
| 107 | + | |
| 108 | + a_val ^= 0x55; | |
| 109 | + | |
| 110 | + t = (short) ((a_val & QUANT_MASK) << 4); | |
| 111 | + seg = (short) ((a_val & SEG_MASK) >> SEG_SHIFT); | |
| 112 | + switch (seg) { | |
| 113 | + case 0: | |
| 114 | + t += 8; | |
| 115 | + break; | |
| 116 | + case 1: | |
| 117 | + t += 0x108; | |
| 118 | + break; | |
| 119 | + default: | |
| 120 | + t += 0x108; | |
| 121 | + t <<= seg - 1; | |
| 122 | + } | |
| 123 | + return (a_val & SIGN_BIT) != 0 ? t : (short) -t; | |
| 124 | + } | |
| 125 | + | |
| 126 | + // 由G.711转至PCM | |
| 127 | + public static byte[] _toPCM(byte[] g711data) { | |
| 128 | + byte[] pcmdata = new byte[g711data.length * 2]; | |
| 129 | + for (int i = 0, k = 0; i < g711data.length; i++) { | |
| 130 | + short v = alaw2linear(g711data[i]); | |
| 131 | + pcmdata[k++] = (byte) (v & 0xff); | |
| 132 | + pcmdata[k++] = (byte) ((v >> 8) & 0xff); | |
| 133 | + } | |
| 134 | + return pcmdata; | |
| 135 | + } | |
| 136 | + | |
| 137 | + // 由PCM转至G.711 | |
| 138 | + public static byte[] _fromPCM(byte[] pcmData) { | |
| 139 | + return encode(pcmData); | |
| 140 | + } | |
| 141 | + | |
| 142 | + @Override | |
| 143 | + public byte[] toPCM(byte[] data) { | |
| 144 | + byte[] temp; | |
| 145 | + // 如果前四字节是00 01 52 00,则是海思头,需要去掉 | |
| 146 | + if (data[0] == 0x00 && data[1] == 0x01 && (data[2] & 0xff) == (data.length - 4) / 2 && data[3] == 0x00) { | |
| 147 | + temp = new byte[data.length - 4]; | |
| 148 | + System.arraycopy(data, 4, temp, 0, temp.length); | |
| 149 | + } else temp = data; | |
| 150 | + | |
| 151 | + return _toPCM(temp); | |
| 152 | + } | |
| 153 | + | |
| 154 | + @Override | |
| 155 | + public byte[] fromPCM(byte[] data) { | |
| 156 | + return encode(data); | |
| 157 | + } | |
| 158 | +} | |
| 0 | 159 | \ No newline at end of file | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/G711UCodec.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/G711UCodec.java | |
| 1 | +package cn.org.hentai.jtt1078.codec; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.util.ByteUtils; | |
| 4 | + | |
| 5 | +import java.io.ByteArrayOutputStream; | |
| 6 | +import java.io.FileInputStream; | |
| 7 | +import java.io.FileOutputStream; | |
| 8 | +/* | |
| 9 | + * This source code is a product of Sun Microsystems, Inc. and is provided | |
| 10 | + * for unrestricted use. Users may copy or modify this source code without | |
| 11 | + * charge. | |
| 12 | + * | |
| 13 | + * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING | |
| 14 | + * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR | |
| 15 | + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. | |
| 16 | + * | |
| 17 | + * Sun source code is provided with no support and without any obligation on | |
| 18 | + * the part of Sun Microsystems, Inc. to assist in its use, correction, | |
| 19 | + * modification or enhancement. | |
| 20 | + * | |
| 21 | + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE | |
| 22 | + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE | |
| 23 | + * OR ANY PART THEREOF. | |
| 24 | + * | |
| 25 | + * In no event will Sun Microsystems, Inc. be liable for any lost revenue | |
| 26 | + * or profits or other special, indirect and consequential damages, even if | |
| 27 | + * Sun has been advised of the possibility of such damages. | |
| 28 | + * | |
| 29 | + * Sun Microsystems, Inc. | |
| 30 | + * 2550 Garcia Avenue | |
| 31 | + * Mountain View, California 94043 | |
| 32 | + */ | |
| 33 | + | |
| 34 | +/** | |
| 35 | + * Created by houcheng on 2019-12-11. | |
| 36 | + */ | |
| 37 | +public class G711UCodec extends AudioCodec | |
| 38 | +{ | |
| 39 | + static final int ULAW = 1; | |
| 40 | + static final int ALAW = 2; | |
| 41 | + | |
| 42 | + /* 16384 entries per table (16 bit) */ | |
| 43 | + static byte[] linear_to_ulaw = new byte[65536]; | |
| 44 | + | |
| 45 | + /* 16384 entries per table (8 bit) */ | |
| 46 | + static short[] ulaw_to_linear = new short[256]; | |
| 47 | + | |
| 48 | + static final int SIGN_BIT = 0x80; | |
| 49 | + static final int QUANT_MASK = 0x0f; | |
| 50 | + static final int NSEGS = 0x08; | |
| 51 | + static final int SEG_SHIFT = 0x04; | |
| 52 | + static final int SEG_MASK = 0x70; | |
| 53 | + | |
| 54 | + static final int BIAS = 0x84; | |
| 55 | + static final int CLIP = 8159; | |
| 56 | + | |
| 57 | + static short[] seg_aend = { 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF }; | |
| 58 | + static short[] seg_uend = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF }; | |
| 59 | + | |
| 60 | + int[] _u2a = { /* u- to A-law conversions */ | |
| 61 | + 1, 1, 2, 2, 3, 3, 4, 4, | |
| 62 | + 5, 5, 6, 6, 7, 7, 8, 8, | |
| 63 | + 9, 10, 11, 12, 13, 14, 15, 16, | |
| 64 | + 17, 18, 19, 20, 21, 22, 23, 24, | |
| 65 | + 25, 27, 29, 31, 33, 34, 35, 36, | |
| 66 | + 37, 38, 39, 40, 41, 42, 43, 44, | |
| 67 | + 46, 48, 49, 50, 51, 52, 53, 54, | |
| 68 | + 55, 56, 57, 58, 59, 60, 61, 62, | |
| 69 | + 64, 65, 66, 67, 68, 69, 70, 71, | |
| 70 | + 72, 73, 74, 75, 76, 77, 78, 79, | |
| 71 | + /* corrected: | |
| 72 | + 81, 82, 83, 84, 85, 86, 87, 88, | |
| 73 | + should be: */ | |
| 74 | + 80, 82, 83, 84, 85, 86, 87, 88, | |
| 75 | + 89, 90, 91, 92, 93, 94, 95, 96, | |
| 76 | + 97, 98, 99, 100, 101, 102, 103, 104, | |
| 77 | + 105, 106, 107, 108, 109, 110, 111, 112, | |
| 78 | + 113, 114, 115, 116, 117, 118, 119, 120, | |
| 79 | + 121, 122, 123, 124, 125, 126, 127, 128}; | |
| 80 | + | |
| 81 | + int[] _a2u = { /* A- to u-law conversions */ | |
| 82 | + 1, 3, 5, 7, 9, 11, 13, 15, | |
| 83 | + 16, 17, 18, 19, 20, 21, 22, 23, | |
| 84 | + 24, 25, 26, 27, 28, 29, 30, 31, | |
| 85 | + 32, 32, 33, 33, 34, 34, 35, 35, | |
| 86 | + 36, 37, 38, 39, 40, 41, 42, 43, | |
| 87 | + 44, 45, 46, 47, 48, 48, 49, 49, | |
| 88 | + 50, 51, 52, 53, 54, 55, 56, 57, | |
| 89 | + 58, 59, 60, 61, 62, 63, 64, 64, | |
| 90 | + 65, 66, 67, 68, 69, 70, 71, 72, | |
| 91 | + /* corrected: | |
| 92 | + 73, 74, 75, 76, 77, 78, 79, 79, | |
| 93 | + should be: */ | |
| 94 | + 73, 74, 75, 76, 77, 78, 79, 80, | |
| 95 | + 80, 81, 82, 83, 84, 85, 86, 87, | |
| 96 | + 88, 89, 90, 91, 92, 93, 94, 95, | |
| 97 | + 96, 97, 98, 99, 100, 101, 102, 103, | |
| 98 | + 104, 105, 106, 107, 108, 109, 110, 111, | |
| 99 | + 112, 113, 114, 115, 116, 117, 118, 119, | |
| 100 | + 120, 121, 122, 123, 124, 125, 126, 127}; | |
| 101 | + | |
| 102 | + static | |
| 103 | + { | |
| 104 | + // 初始化ulaw表 | |
| 105 | + for (int i = 0; i < 256; i++) ulaw_to_linear[i] = ulaw2linear((byte)i); | |
| 106 | + // 初始化ulaw2linear表 | |
| 107 | + for (int i = 0; i < 65535; i++) linear_to_ulaw[i] = linear2ulaw((short)i); | |
| 108 | + } | |
| 109 | + | |
| 110 | + public static short ulaw2linear(byte u_val) | |
| 111 | + { | |
| 112 | + short t; | |
| 113 | + u_val = (byte)(~u_val); | |
| 114 | + t = (short)(((u_val & QUANT_MASK) << 3) + BIAS); | |
| 115 | + t <<= (u_val & SEG_MASK) >>> SEG_SHIFT; | |
| 116 | + | |
| 117 | + return ((u_val & SIGN_BIT) > 0 ? (short)(BIAS - t) : (short)(t - BIAS)); | |
| 118 | + } | |
| 119 | + | |
| 120 | + public static byte linear2ulaw(short pcm_val) | |
| 121 | + { | |
| 122 | + short mask; | |
| 123 | + short seg; | |
| 124 | + byte uval; | |
| 125 | + | |
| 126 | + pcm_val = (short)(pcm_val >> 2); | |
| 127 | + if (pcm_val < 0) | |
| 128 | + { | |
| 129 | + pcm_val = (short)(-pcm_val); | |
| 130 | + mask = 0x7f; | |
| 131 | + } | |
| 132 | + else | |
| 133 | + { | |
| 134 | + mask = 0xff; | |
| 135 | + } | |
| 136 | + | |
| 137 | + if (pcm_val > CLIP) pcm_val = CLIP; | |
| 138 | + pcm_val += (BIAS >> 2); | |
| 139 | + | |
| 140 | + seg = search(pcm_val, seg_uend, (short)8); | |
| 141 | + | |
| 142 | + if (seg >= 8) | |
| 143 | + { | |
| 144 | + return (byte)(0x7f ^ mask); | |
| 145 | + } | |
| 146 | + else | |
| 147 | + { | |
| 148 | + uval = (byte) ((seg << 4) | ((pcm_val >> (seg + 1)) & 0xF)); | |
| 149 | + return (byte)(uval ^ mask); | |
| 150 | + } | |
| 151 | + } | |
| 152 | + | |
| 153 | + static short search(short val, short[] table, short size) | |
| 154 | + { | |
| 155 | + for (short i = 0; i < size; i++) | |
| 156 | + { | |
| 157 | + if (val <= table[i]) return i; | |
| 158 | + } | |
| 159 | + return size; | |
| 160 | + } | |
| 161 | + | |
| 162 | + static void ulaw_to_pcm16(int src_length, byte[] src_samples, byte[] dst_samples) | |
| 163 | + { | |
| 164 | + for (int i = 0, k = 0; i < src_length; i++) | |
| 165 | + { | |
| 166 | + short s = ulaw_to_linear[src_samples[i] & 0xff]; | |
| 167 | + dst_samples[k++] = (byte)(s & 0xff); | |
| 168 | + dst_samples[k++] = (byte)((s >> 8) & 0xff); | |
| 169 | + } | |
| 170 | + } | |
| 171 | + | |
| 172 | + static void pcm16_to_ulaw(int src_length, byte[] src_samples, byte[] dst_samples) | |
| 173 | + { | |
| 174 | + short[] s_samples = ByteUtils.toShortArray(src_samples); | |
| 175 | + for (int i = 0, k = 0; i < s_samples.length; i++) | |
| 176 | + { | |
| 177 | + dst_samples[k++] = linear2ulaw(s_samples[i]); | |
| 178 | + } | |
| 179 | + } | |
| 180 | + | |
| 181 | + @Override | |
| 182 | + public byte[] toPCM(byte[] data) | |
| 183 | + { | |
| 184 | + byte[] temp; | |
| 185 | + // 如果前四字节是00 01 52 00,则是海思头,需要去掉 | |
| 186 | + if (data[0] == 0x00 && data[1] == 0x01 && (data[2] & 0xff) == (data.length - 4) / 2 && data[3] == 0x00) | |
| 187 | + { | |
| 188 | + temp = new byte[data.length - 4]; | |
| 189 | + System.arraycopy(data, 4, temp, 0, temp.length); | |
| 190 | + } | |
| 191 | + else temp = data; | |
| 192 | + | |
| 193 | + byte[] dest = new byte[temp.length * 2]; | |
| 194 | + ulaw_to_pcm16(temp.length, temp, dest); | |
| 195 | + return dest; | |
| 196 | + } | |
| 197 | + | |
| 198 | + @Override | |
| 199 | + public byte[] fromPCM(byte[] data) | |
| 200 | + { | |
| 201 | + byte[] dest = new byte[data.length / 2]; | |
| 202 | + pcm16_to_ulaw(data.length, data, dest); | |
| 203 | + return dest; | |
| 204 | + } | |
| 205 | + | |
| 206 | + public static void main(String[] args) throws Exception | |
| 207 | + { | |
| 208 | + FileInputStream fis = new FileInputStream("d:\\fuck121212121.pcm"); | |
| 209 | + int len = -1; | |
| 210 | + byte[] buff = new byte[320]; | |
| 211 | + AudioCodec codec = new G711UCodec(); | |
| 212 | + ByteArrayOutputStream baos = new ByteArrayOutputStream(4096 * 1024); | |
| 213 | + while ((len = fis.read(buff)) > -1) | |
| 214 | + { | |
| 215 | + baos.write(buff, 0, len); | |
| 216 | + } | |
| 217 | + new FileOutputStream("D:\\temp\\fuckfuckfuck.g711u").write(codec.fromPCM(baos.toByteArray())); | |
| 218 | + } | |
| 219 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/G726Codec.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/G726Codec.java | |
| 1 | +package cn.org.hentai.jtt1078.codec; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.codec.g726.*; | |
| 4 | + | |
| 5 | +import java.io.FileInputStream; | |
| 6 | +import java.io.FileOutputStream; | |
| 7 | + | |
| 8 | +public class G726Codec extends AudioCodec { | |
| 9 | + | |
| 10 | + // pcm采样率 | |
| 11 | + private static final int PCM_SAMPLE = 8000; | |
| 12 | + | |
| 13 | + // pcm采样点 | |
| 14 | + private static final int PCM_POINT = 320; | |
| 15 | + | |
| 16 | + // 音频通道数 | |
| 17 | + private static final int CHANNEL = 1; | |
| 18 | + | |
| 19 | + // 码率 | |
| 20 | + private static final int G726_BIT_RATE_16000 = 16000; | |
| 21 | + private static final int G726_BIT_RATE_24000 = 24000; | |
| 22 | + private static final int G726_BIT_RATE_32000 = 32000; | |
| 23 | + private static final int G726_BIT_RATE_40000 = 40000; | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + @Override | |
| 28 | + public byte[] toPCM(byte[] data) { | |
| 29 | + int pos = 0; | |
| 30 | + // 如果前四字节是00 01 52 00,则是海思头,需要去掉 | |
| 31 | + if (data[0] == 0x00 && data[1] == 0x01 && (data[2] & 0xff) == (data.length - 4) / 2 && data[3] == 0x00) { | |
| 32 | + pos = 4; | |
| 33 | + } | |
| 34 | + | |
| 35 | + int length = data.length - pos; | |
| 36 | + | |
| 37 | + int point = PCM_POINT; | |
| 38 | + | |
| 39 | + // 计算G726的码率 | |
| 40 | + int rateBit = length * 8 * PCM_SAMPLE/point; | |
| 41 | + | |
| 42 | + G726 g726 = null; | |
| 43 | + | |
| 44 | + // 码率 | |
| 45 | + if (rateBit == G726_BIT_RATE_40000) { | |
| 46 | + g726 = new G726_40(); | |
| 47 | + } | |
| 48 | + else if (rateBit == G726_BIT_RATE_32000) { | |
| 49 | + g726 = new G726_32(); | |
| 50 | + } | |
| 51 | + else if (rateBit == G726_BIT_RATE_24000) { | |
| 52 | + g726 = new G726_24(); | |
| 53 | + } | |
| 54 | + else if (rateBit == G726_BIT_RATE_16000) { | |
| 55 | + g726 = new G726_16(); | |
| 56 | + } | |
| 57 | + else { | |
| 58 | + return null; | |
| 59 | + } | |
| 60 | + | |
| 61 | + int pcmSize = point * CHANNEL * 2; | |
| 62 | + byte[] pcm = new byte[pcmSize]; | |
| 63 | + | |
| 64 | + int ret = g726.decode(data,pos,length,G726.AUDIO_ENCODING_LINEAR,pcm,0); | |
| 65 | + if (ret < 0) { | |
| 66 | + return null; | |
| 67 | + } | |
| 68 | + return pcm; | |
| 69 | + } | |
| 70 | + | |
| 71 | + @Override | |
| 72 | + public byte[] fromPCM(byte[] data) { | |
| 73 | + // TODO: | |
| 74 | + return new byte[0]; | |
| 75 | + } | |
| 76 | + | |
| 77 | + private static void readWrite(String in,String out,int size) throws Exception { | |
| 78 | + FileInputStream f = new FileInputStream(in); | |
| 79 | + FileOutputStream o = new FileOutputStream(out); | |
| 80 | + int len = -1; | |
| 81 | + byte[] buff = new byte[size]; | |
| 82 | + G726Codec g726Codec = new G726Codec(); | |
| 83 | + int index = 0; | |
| 84 | + while ((len = f.read(buff,index,buff.length)) > -1) { | |
| 85 | + o.write(g726Codec.toPCM(buff)); | |
| 86 | + } | |
| 87 | + } | |
| 88 | + | |
| 89 | + // mac下在终端中输入 /Applications/VLC.app/Contents/MacOS/VLC --demux=rawaud --rawaud-channels 1 --rawaud-samplerate 8000 ${path} | |
| 90 | + // 修改${path} 的值为pcm路径,即可播放转码后的pcm文件 | |
| 91 | + public static void main(String[] args) throws Exception { | |
| 92 | + | |
| 93 | + readWrite(Thread.currentThread().getContextClassLoader().getResource("g726/in_40.g726").getPath(), | |
| 94 | + "/Users/tmyam/Downloads/out_40.pcm",200); | |
| 95 | + | |
| 96 | + | |
| 97 | + readWrite(Thread.currentThread().getContextClassLoader().getResource("g726/in_32.g726").getPath(), | |
| 98 | + "/Users/tmyam/Downloads/out_32.pcm",160); | |
| 99 | + | |
| 100 | + | |
| 101 | + readWrite(Thread.currentThread().getContextClassLoader().getResource("g726/in_24.g726").getPath(), | |
| 102 | + "/Users/tmyam/Downloads/out_24.pcm",120); | |
| 103 | + | |
| 104 | + | |
| 105 | + readWrite(Thread.currentThread().getContextClassLoader().getResource("g726/in_16.g726").getPath(), | |
| 106 | + "/Users/tmyam/Downloads/out_16.pcm",80); | |
| 107 | + } | |
| 108 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/MP3Encoder.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/MP3Encoder.java | |
| 1 | +package cn.org.hentai.jtt1078.codec; | |
| 2 | + | |
| 3 | +import de.sciss.jump3r.lowlevel.LameEncoder; | |
| 4 | +import de.sciss.jump3r.mp3.Lame; | |
| 5 | + | |
| 6 | +import javax.sound.sampled.AudioFormat; | |
| 7 | +import java.io.ByteArrayOutputStream; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * Created by matrixy on 2020/4/27. | |
| 11 | + */ | |
| 12 | +public class MP3Encoder | |
| 13 | +{ | |
| 14 | + static final AudioFormat PCM_FORMAT = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000, 16, 1, 1 * 2, -1, false); | |
| 15 | + | |
| 16 | + byte[] buffer = null; | |
| 17 | + ByteArrayOutputStream mp3Data; | |
| 18 | + LameEncoder encoder = null; | |
| 19 | + | |
| 20 | + public MP3Encoder() | |
| 21 | + { | |
| 22 | + encoder = new LameEncoder(PCM_FORMAT, 256, 3, Lame.MEDIUM, false); | |
| 23 | + buffer = new byte[encoder.getPCMBufferSize()]; | |
| 24 | + mp3Data = new ByteArrayOutputStream(encoder.getOutputBufferSize()); | |
| 25 | + } | |
| 26 | + | |
| 27 | + public byte[] encode(byte[] pcm) | |
| 28 | + { | |
| 29 | + if (pcm == null) return null; | |
| 30 | + int bytesToTransfer = Math.min(encoder.getPCMBufferSize(), pcm.length); | |
| 31 | + int bytesWritten; | |
| 32 | + int currentPcmPosition = 0; | |
| 33 | + | |
| 34 | + mp3Data.reset(); | |
| 35 | + | |
| 36 | + while (0 < (bytesWritten = encoder.encodeBuffer(pcm, currentPcmPosition, bytesToTransfer, buffer))) | |
| 37 | + { | |
| 38 | + currentPcmPosition += bytesToTransfer; | |
| 39 | + bytesToTransfer = Math.min(buffer.length, pcm.length - currentPcmPosition); | |
| 40 | + | |
| 41 | + mp3Data.write(buffer, 0, bytesWritten); | |
| 42 | + } | |
| 43 | + | |
| 44 | + return mp3Data.toByteArray(); | |
| 45 | + } | |
| 46 | + | |
| 47 | + public void close() | |
| 48 | + { | |
| 49 | + encoder.close(); | |
| 50 | + } | |
| 51 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/SilenceCodec.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/SilenceCodec.java | |
| 1 | +package cn.org.hentai.jtt1078.codec; | |
| 2 | + | |
| 3 | +import java.util.Arrays; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * Created by houcheng on 2019-12-11. | |
| 7 | + */ | |
| 8 | +public class SilenceCodec extends AudioCodec | |
| 9 | +{ | |
| 10 | + static final byte[] BLANK = new byte[0]; | |
| 11 | + | |
| 12 | + @Override | |
| 13 | + public byte[] toPCM(byte[] data) | |
| 14 | + { | |
| 15 | + return BLANK; | |
| 16 | + } | |
| 17 | + | |
| 18 | + @Override | |
| 19 | + public byte[] fromPCM(byte[] data) | |
| 20 | + { | |
| 21 | + return BLANK; | |
| 22 | + } | |
| 23 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/g726/G726.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/g726/G726.java | |
| 1 | + | |
| 2 | +package cn.org.hentai.jtt1078.codec.g726; | |
| 3 | + | |
| 4 | + | |
| 5 | +import cn.org.hentai.jtt1078.codec.G711Codec; | |
| 6 | +import cn.org.hentai.jtt1078.codec.G711UCodec; | |
| 7 | + | |
| 8 | +/** Common routines for G.721 and G.723 conversions. | |
| 9 | + * <p> | |
| 10 | + * This implementation is based on the ANSI-C language reference implementations | |
| 11 | + * of the CCITT (International Telegraph and Telephone Consultative Committee) | |
| 12 | + * G.711, G.721 and G.723 voice compressions, provided by Sun Microsystems, Inc. | |
| 13 | + * <p> | |
| 14 | + * Acknowledgement to Sun Microsystems, Inc. for having released the original | |
| 15 | + * ANSI-C source code to the public domain. | |
| 16 | + */ | |
| 17 | +public abstract class G726 { | |
| 18 | + | |
| 19 | + // ##### C-to-Java conversion: ##### | |
| 20 | + // short becomes int | |
| 21 | + // char becomes int | |
| 22 | + // unsigned char becomes int | |
| 23 | + | |
| 24 | + | |
| 25 | + // *************************** STATIC *************************** | |
| 26 | + | |
| 27 | + /** ISDN u-law */ | |
| 28 | + public static final int AUDIO_ENCODING_ULAW=1; | |
| 29 | + | |
| 30 | + /** ISDN A-law */ | |
| 31 | + public static final int AUDIO_ENCODING_ALAW=2; | |
| 32 | + | |
| 33 | + /** PCM 2's-complement (0-center) */ | |
| 34 | + public static final int AUDIO_ENCODING_LINEAR=3; | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + /** The first 15 values, powers of 2. */ | |
| 39 | + private static final /*short*/int[] power2 = { 1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000 }; | |
| 40 | + | |
| 41 | + | |
| 42 | + /** Quantizes the input val against the table of size short integers. | |
| 43 | + * It returns i if table[i-1]<=val<table[i]. | |
| 44 | + * <p> | |
| 45 | + * Using linear search for simple coding. */ | |
| 46 | + private static int quan(int val, /*short*/int[] table, int size) { | |
| 47 | + | |
| 48 | + int i; | |
| 49 | + for (i=0; i<size; i++) if (val<table[i]) break; | |
| 50 | + return i; | |
| 51 | + } | |
| 52 | + | |
| 53 | + | |
| 54 | + /** Given a raw sample, 'd', of the difference signal and a | |
| 55 | + * quantization step size scale factor, 'y', this routine returns the | |
| 56 | + * ADPCM codeword to which that sample gets quantized. The step | |
| 57 | + * size scale factor division operation is done in the log base 2 domain | |
| 58 | + * as a subtraction. | |
| 59 | + * <br> | |
| 60 | + * @param d - Raw difference signal sample | |
| 61 | + * @param y - Step size multiplier | |
| 62 | + * @param table - Quantization table | |
| 63 | + * @param size - Table size of short integers | |
| 64 | + */ | |
| 65 | + protected static int quantize(int d, int y, /*short*/int[] table, int size) { | |
| 66 | + | |
| 67 | + /* LOG | |
| 68 | + * Compute base 2 log of 'd', and store in 'dl'. | |
| 69 | + */ | |
| 70 | + /*short*/int dqm=Math.abs(d); /* Magnitude of 'd' */ | |
| 71 | + | |
| 72 | + /*short*/int exp=quan(dqm>>1, power2, 15); /* Integer part of base 2 log of 'd' */ | |
| 73 | + | |
| 74 | + /*short*/int mant=((dqm<<7)>>exp)&0x7F; /* Fractional part of base 2 log */ | |
| 75 | + | |
| 76 | + /*short*/int dl=(exp<<7)+mant; /* Log of magnitude of 'd' */ | |
| 77 | + | |
| 78 | + /* SUBTB | |
| 79 | + * "Divide" by step size multiplier. | |
| 80 | + */ | |
| 81 | + /* Step size scale factor normalized log */ | |
| 82 | + /*short*/int dln=dl-(y>>2); | |
| 83 | + | |
| 84 | + /* QUAN | |
| 85 | + * Obtain codword i for 'd'. | |
| 86 | + */ | |
| 87 | + int i=quan(dln, table, size); | |
| 88 | + if (d<0) /* take 1's complement of i */ | |
| 89 | + return ((size<<1)+1-i); | |
| 90 | + else if (i==0) /* take 1's complement of 0 */ | |
| 91 | + return ((size<<1)+1); /* new in 1988 */ | |
| 92 | + else | |
| 93 | + return (i); | |
| 94 | + } | |
| 95 | + | |
| 96 | + | |
| 97 | + /** Returns reconstructed difference signal 'dq' obtained from | |
| 98 | + * codeword 'i' and quantization step size scale factor 'y'. | |
| 99 | + * Multiplication is performed in log base 2 domain as addition. | |
| 100 | + * @param sign - 0 for non-negative value | |
| 101 | + * @param dqln - G.72x codeword | |
| 102 | + * @param y - Step size multiplier | |
| 103 | + */ | |
| 104 | + protected static int reconstruct(int sign, int dqln, int y) { | |
| 105 | + | |
| 106 | + /* Log of 'dq' magnitude */ | |
| 107 | + /*short*/int dql=dqln+(y>>2); /* ADDA */ | |
| 108 | + | |
| 109 | + if (dql<0) { | |
| 110 | + return ((sign!=0)? -0x8000 : 0); | |
| 111 | + } | |
| 112 | + else { | |
| 113 | + /* ANTILOG */ | |
| 114 | + /* Integer part of log */ | |
| 115 | + /*short*/int dex=(dql>>7)&15; | |
| 116 | + /*short*/int dqt=128+(dql&127); | |
| 117 | + /* Reconstructed difference signal sample */ | |
| 118 | + /*short*/int dq=(dqt<<7)>>(14-dex); | |
| 119 | + return ((sign!=0)? (dq-0x8000) : dq); | |
| 120 | + } | |
| 121 | + } | |
| 122 | + | |
| 123 | + | |
| 124 | + /** updates the state variables for each output code | |
| 125 | + * @param code_size - distinguish 723_40 with others | |
| 126 | + * @param y - quantizer step size | |
| 127 | + * @param wi - scale factor multiplier | |
| 128 | + * @param fi - for long/short term energies | |
| 129 | + * @param dq - quantized prediction difference | |
| 130 | + * @param sr - reconstructed signal | |
| 131 | + * @param dqsez - difference from 2-pole predictor | |
| 132 | + * @param state - coder state | |
| 133 | + */ | |
| 134 | + protected static void update(int code_size, int y, int wi, int fi, int dq, int sr, int dqsez, G726State state) { | |
| 135 | + | |
| 136 | + int cnt; | |
| 137 | + /*short*/int mag, exp, mant; /* Adaptive predictor, FLOAT A */ | |
| 138 | + /*short*/int a2p; /* LIMC */ | |
| 139 | + /*short*/int a1ul; /* UPA1 */ | |
| 140 | + /*short*/int ua2, pks1; /* UPA2 */ | |
| 141 | + /*short*/int uga2a, fa1; | |
| 142 | + /*short*/int uga2b; | |
| 143 | + /*char*/int tr; /* tone/transition detector */ | |
| 144 | + /*short*/int ylint, thr2, dqthr; | |
| 145 | + /*short*/int ylfrac, thr1; | |
| 146 | + /*short*/int pk0; | |
| 147 | + | |
| 148 | + // ##### C-to-Java conversion: ##### | |
| 149 | + // init a2p | |
| 150 | + a2p=0; | |
| 151 | + | |
| 152 | + pk0=(dqsez<0)? 1 : 0; /* needed in updating predictor poles */ | |
| 153 | + | |
| 154 | + mag=dq&0x7FFF; /* prediction difference magnitude */ | |
| 155 | + /* TRANS */ | |
| 156 | + ylint=state.yl>>15; /* exponent part of yl */ | |
| 157 | + ylfrac=(state.yl>>10)&0x1F; /* fractional part of yl */ | |
| 158 | + thr1=(32+ylfrac)<<ylint; /* threshold */ | |
| 159 | + thr2=(ylint>9)? 31<<10 : thr1; /* limit thr2 to 31<<10 */ | |
| 160 | + dqthr=(thr2+(thr2>>1))>>1; /* dqthr=0.75 * thr2 */ | |
| 161 | + if (state.td==0) /* signal supposed voice */ | |
| 162 | + tr=0; | |
| 163 | + else | |
| 164 | + if (mag<=dqthr) /* supposed data, but small mag */ | |
| 165 | + tr=0; /* treated as voice */ | |
| 166 | + else /* signal is data (modem) */ | |
| 167 | + tr=1; | |
| 168 | + | |
| 169 | + /* Quantizer scale factor adaptation. */ | |
| 170 | + | |
| 171 | + /* FUNCTW&FILTD&DELAY */ | |
| 172 | + /* update non-steady state step size multiplier */ | |
| 173 | + state.yu=y+((wi-y)>>5); | |
| 174 | + | |
| 175 | + /* LIMB */ | |
| 176 | + if (state.yu<544) /* 544<=yu<=5120 */ | |
| 177 | + state.yu=544; | |
| 178 | + else | |
| 179 | + if (state.yu>5120) | |
| 180 | + state.yu=5120; | |
| 181 | + | |
| 182 | + /* FILTE&DELAY */ | |
| 183 | + /* update steady state step size multiplier */ | |
| 184 | + state.yl+=state.yu+((-state.yl)>>6); | |
| 185 | + | |
| 186 | + /* | |
| 187 | + * Adaptive predictor coefficients. | |
| 188 | + */ | |
| 189 | + if (tr==1) { | |
| 190 | + /* reset a's and b's for modem signal */ | |
| 191 | + state.a[0]=0; | |
| 192 | + state.a[1]=0; | |
| 193 | + state.b[0]=0; | |
| 194 | + state.b[1]=0; | |
| 195 | + state.b[2]=0; | |
| 196 | + state.b[3]=0; | |
| 197 | + state.b[4]=0; | |
| 198 | + state.b[5]=0; | |
| 199 | + } | |
| 200 | + else { | |
| 201 | + /* update a's and b's */ | |
| 202 | + pks1=pk0^state.pk[0]; /* UPA2 */ | |
| 203 | + | |
| 204 | + /* update predictor pole a[1] */ | |
| 205 | + a2p=state.a[1]-(state.a[1]>>7); | |
| 206 | + if (dqsez != 0) { | |
| 207 | + fa1=(pks1!=0)? state.a[0] : -state.a[0]; | |
| 208 | + if (fa1<-8191) /* a2p=function of fa1 */ | |
| 209 | + a2p-=0x100; | |
| 210 | + else | |
| 211 | + if (fa1>8191) | |
| 212 | + a2p+=0xFF; | |
| 213 | + else | |
| 214 | + a2p+=fa1>>5; | |
| 215 | + | |
| 216 | + if ((pk0^state.pk[1])!=0) { | |
| 217 | + /* LIMC */ | |
| 218 | + if (a2p<=-12160) | |
| 219 | + a2p=-12288; | |
| 220 | + else | |
| 221 | + if (a2p>=12416) | |
| 222 | + a2p=12288; | |
| 223 | + else | |
| 224 | + a2p-=0x80; | |
| 225 | + } | |
| 226 | + else | |
| 227 | + if (a2p<=-12416) | |
| 228 | + a2p=-12288; | |
| 229 | + else | |
| 230 | + if (a2p>=12160) | |
| 231 | + a2p=12288; | |
| 232 | + else | |
| 233 | + a2p+=0x80; | |
| 234 | + } | |
| 235 | + | |
| 236 | + /* TRIGB&DELAY */ | |
| 237 | + state.a[1]=a2p; | |
| 238 | + | |
| 239 | + /* UPA1 */ | |
| 240 | + /* update predictor pole a[0] */ | |
| 241 | + state.a[0] -= state.a[0]>>8; | |
| 242 | + if (dqsez != 0) | |
| 243 | + if (pks1==0) | |
| 244 | + state.a[0]+=192; | |
| 245 | + else | |
| 246 | + state.a[0] -= 192; | |
| 247 | + | |
| 248 | + /* LIMD */ | |
| 249 | + a1ul=15360-a2p; | |
| 250 | + if (state.a[0]<-a1ul) | |
| 251 | + state.a[0]=-a1ul; | |
| 252 | + else if (state.a[0]>a1ul) | |
| 253 | + state.a[0]=a1ul; | |
| 254 | + | |
| 255 | + /* UPB : update predictor zeros b[6] */ | |
| 256 | + for (cnt=0; cnt<6; cnt++) { | |
| 257 | + | |
| 258 | + if (code_size==5) /* for 40Kbps G.723 */ | |
| 259 | + state.b[cnt]-=state.b[cnt]>>9; | |
| 260 | + else /* for G.721 and 24Kbps G.723 */ | |
| 261 | + state.b[cnt]-=state.b[cnt]>>8; | |
| 262 | + if ((dq&0x7FFF)!=0) { | |
| 263 | + /* XOR */ | |
| 264 | + if ((dq^state.dq[cnt])>=0) | |
| 265 | + state.b[cnt]+=128; | |
| 266 | + else | |
| 267 | + state.b[cnt]-=128; | |
| 268 | + } | |
| 269 | + } | |
| 270 | + } | |
| 271 | + | |
| 272 | + for (cnt=5; cnt>0; cnt--) state.dq[cnt]=state.dq[cnt-1]; | |
| 273 | + /* FLOAT A : convert dq[0] to 4-bit exp, 6-bit mantissa f.p. */ | |
| 274 | + if (mag==0) { | |
| 275 | + state.dq[0]=(dq>=0)? 0x20 : 0xFC20; | |
| 276 | + } | |
| 277 | + else { | |
| 278 | + exp=quan(mag, power2, 15); | |
| 279 | + state.dq[0]=(dq>=0) ? (exp<<6)+((mag<<6)>>exp) : (exp<<6)+((mag<<6)>>exp)-0x400; | |
| 280 | + } | |
| 281 | + | |
| 282 | + state.sr[1]=state.sr[0]; | |
| 283 | + /* FLOAT B : convert sr to 4-bit exp., 6-bit mantissa f.p. */ | |
| 284 | + if (sr==0) { | |
| 285 | + state.sr[0]=0x20; | |
| 286 | + } | |
| 287 | + else | |
| 288 | + if (sr>0) { | |
| 289 | + exp=quan(sr, power2, 15); | |
| 290 | + state.sr[0]=(exp<<6)+((sr<<6)>>exp); | |
| 291 | + } | |
| 292 | + else | |
| 293 | + if (sr>-32768) { | |
| 294 | + mag=-sr; | |
| 295 | + exp=quan(mag, power2, 15); | |
| 296 | + state.sr[0]=(exp<<6)+((mag<<6)>>exp)-0x400; | |
| 297 | + } | |
| 298 | + else | |
| 299 | + state.sr[0]=0xFC20; | |
| 300 | + | |
| 301 | + /* DELAY A */ | |
| 302 | + state.pk[1]=state.pk[0]; | |
| 303 | + state.pk[0]=pk0; | |
| 304 | + | |
| 305 | + /* TONE */ | |
| 306 | + if (tr==1) /* this sample has been treated as data */ | |
| 307 | + state.td=0; /* next one will be treated as voice */ | |
| 308 | + else | |
| 309 | + if (a2p<-11776) /* small sample-to-sample correlation */ | |
| 310 | + state.td=1; /* signal may be data */ | |
| 311 | + else /* signal is voice */ | |
| 312 | + state.td=0; | |
| 313 | + | |
| 314 | + /* | |
| 315 | + * Adaptation speed control. | |
| 316 | + */ | |
| 317 | + state.dms+=(fi-state.dms)>>5; /* FILTA */ | |
| 318 | + state.dml+=(((fi<<2)-state.dml)>>7); /* FILTB */ | |
| 319 | + | |
| 320 | + if (tr==1) | |
| 321 | + state.ap=256; | |
| 322 | + else | |
| 323 | + if (y<1536) /* SUBTC */ | |
| 324 | + state.ap+=(0x200-state.ap)>>4; | |
| 325 | + else | |
| 326 | + if (state.td==1) | |
| 327 | + state.ap+=(0x200-state.ap)>>4; | |
| 328 | + else | |
| 329 | + if (Math.abs((state.dms<<2)-state.dml)>=(state.dml>>3)) | |
| 330 | + state.ap+=(0x200-state.ap)>>4; | |
| 331 | + else | |
| 332 | + state.ap+=(-state.ap)>>4; | |
| 333 | + } | |
| 334 | + | |
| 335 | + /** At the end of ADPCM decoding, it simulates an encoder which may be receiving | |
| 336 | + * the output of this decoder as a tandem process. If the output of the | |
| 337 | + * simulated encoder differs from the input to this decoder, the decoder output | |
| 338 | + * is adjusted by one level of A-law or u-law codes. | |
| 339 | + * | |
| 340 | + * @param sr - decoder output linear PCM sample, | |
| 341 | + * @param se - predictor estimate sample, | |
| 342 | + * @param y - quantizer step size, | |
| 343 | + * @param i - decoder input code, | |
| 344 | + * @param sign - sign bit of code i | |
| 345 | + * | |
| 346 | + * @return adjusted A-law or u-law compressed sample. | |
| 347 | + */ | |
| 348 | + protected static int tandem_adjust_alaw(int sr, int se, int y, int i, int sign, /*short*/int[] qtab) { | |
| 349 | + | |
| 350 | + /*unsigned char*/int sp; /* A-law compressed 8-bit code */ | |
| 351 | + /*short*/int dx; /* prediction error */ | |
| 352 | + /*char*/int id; /* quantized prediction error */ | |
| 353 | + int sd; /* adjusted A-law decoded sample value */ | |
| 354 | + int im; /* biased magnitude of i */ | |
| 355 | + int imx; /* biased magnitude of id */ | |
| 356 | + | |
| 357 | + if (sr<=-32768) sr=-1; | |
| 358 | + sp= G711Codec.linear2alaw((short)((sr>>1)<<3)); /* short to A-law compression */ | |
| 359 | + dx=(G711Codec.alaw2linear((byte)sp)>>2)-se; /* 16-bit prediction error */ | |
| 360 | + id=quantize(dx, y, qtab, sign-1); | |
| 361 | + | |
| 362 | + if (id==i) { | |
| 363 | + /* no adjustment on sp */ | |
| 364 | + return (sp); | |
| 365 | + } | |
| 366 | + else { | |
| 367 | + /* sp adjustment needed */ | |
| 368 | + /* ADPCM codes : 8, 9, ... F, 0, 1, ... , 6, 7 */ | |
| 369 | + im=i^sign; /* 2's complement to biased unsigned */ | |
| 370 | + imx=id^sign; | |
| 371 | + | |
| 372 | + if (imx>im) { | |
| 373 | + /* sp adjusted to next lower value */ | |
| 374 | + if ((sp&0x80)!=0) { | |
| 375 | + sd=(sp==0xD5)? 0x55 : ((sp^0x55)-1)^0x55; | |
| 376 | + } | |
| 377 | + else { | |
| 378 | + sd=(sp==0x2A)? 0x2A : ((sp^0x55)+1)^0x55; | |
| 379 | + } | |
| 380 | + } | |
| 381 | + else { | |
| 382 | + /* sp adjusted to next higher value */ | |
| 383 | + if ((sp&0x80)!=0) | |
| 384 | + sd=(sp==0xAA)? 0xAA : ((sp^0x55)+1)^0x55; | |
| 385 | + else | |
| 386 | + sd=(sp==0x55)? 0xD5 : ((sp^0x55)-1)^0x55; | |
| 387 | + } | |
| 388 | + return (sd); | |
| 389 | + } | |
| 390 | + } | |
| 391 | + | |
| 392 | + /** @param sr - decoder output linear PCM sample | |
| 393 | + * @param se - predictor estimate sample | |
| 394 | + * @param y - quantizer step size | |
| 395 | + * @param i - decoder input code | |
| 396 | + * @param sign | |
| 397 | + * @param qtab | |
| 398 | + */ | |
| 399 | + protected static int tandem_adjust_ulaw(int sr, int se, int y, int i, int sign, /*short*/int[] qtab) { | |
| 400 | + | |
| 401 | + /*unsigned char*/int sp; /* u-law compressed 8-bit code */ | |
| 402 | + /*short*/int dx; /* prediction error */ | |
| 403 | + /*char*/int id; /* quantized prediction error */ | |
| 404 | + int sd; /* adjusted u-law decoded sample value */ | |
| 405 | + int im; /* biased magnitude of i */ | |
| 406 | + int imx; /* biased magnitude of id */ | |
| 407 | + | |
| 408 | + if (sr<=-32768) sr=0; | |
| 409 | + sp= G711UCodec.linear2ulaw((short)(sr<<2)); /* short to u-law compression */ | |
| 410 | + dx=(G711UCodec.ulaw2linear((byte)sp)>>2)-se; /* 16-bit prediction error */ | |
| 411 | + id=quantize(dx, y, qtab, sign-1); | |
| 412 | + if (id==i) { | |
| 413 | + return (sp); | |
| 414 | + } | |
| 415 | + else { | |
| 416 | + /* ADPCM codes : 8, 9, ... F, 0, 1, ... , 6, 7 */ | |
| 417 | + im=i^sign; /* 2's complement to biased unsigned */ | |
| 418 | + imx=id^sign; | |
| 419 | + if (imx>im) { | |
| 420 | + /* sp adjusted to next lower value */ | |
| 421 | + if ((sp&0x80)!=0) | |
| 422 | + sd=(sp==0xFF)? 0x7E : sp+1; | |
| 423 | + else | |
| 424 | + sd=(sp==0)? 0 : sp-1; | |
| 425 | + | |
| 426 | + } | |
| 427 | + else { | |
| 428 | + /* sp adjusted to next higher value */ | |
| 429 | + if ((sp&0x80)!=0) | |
| 430 | + sd=(sp==0x80)? 0x80 : sp-1; | |
| 431 | + else | |
| 432 | + sd=(sp==0x7F)? 0xFE : sp+1; | |
| 433 | + } | |
| 434 | + return (sd); | |
| 435 | + } | |
| 436 | + } | |
| 437 | + | |
| 438 | + | |
| 439 | + // ##### C-to-Java conversion: ##### | |
| 440 | + | |
| 441 | + /** Converts a byte into an unsigned int. */ | |
| 442 | + protected static int unsignedInt(byte b) { | |
| 443 | + return ((int)b+0x100)&0xFF; | |
| 444 | + } | |
| 445 | + | |
| 446 | + // ##### 2 bytes to int conversion: ##### | |
| 447 | + | |
| 448 | + /** Converts 2 little-endian-bytes into an unsigned int. */ | |
| 449 | + public static int unsignedIntLittleEndian(byte hi_b, byte lo_b) { | |
| 450 | + return (unsignedInt(hi_b)<<8) + unsignedInt(lo_b); | |
| 451 | + } | |
| 452 | + | |
| 453 | + /** Converts 2 little-endian-bytes into a signed int. */ | |
| 454 | + public static int signedIntLittleEndian(byte hi_b, byte lo_b) { | |
| 455 | + int sign_bit=hi_b>>7; | |
| 456 | + return (sign_bit==0)? (unsignedInt(hi_b)<<8) + unsignedInt(lo_b) : (-1^0x7FFF)^(((unsignedInt(hi_b)&0x7F)<<8) + unsignedInt(lo_b)); | |
| 457 | + } | |
| 458 | + | |
| 459 | + | |
| 460 | + // ************************* NON-STATIC ************************* | |
| 461 | + | |
| 462 | + /** Encoding state */ | |
| 463 | + G726State state; | |
| 464 | + | |
| 465 | + int type; | |
| 466 | + | |
| 467 | + /** Creates a new G726 processor, that can be used to encode from or decode do PCM audio data. */ | |
| 468 | + public G726(int type) { | |
| 469 | + this.type = type; | |
| 470 | + state=new G726State(); | |
| 471 | + } | |
| 472 | + | |
| 473 | + public int getType() { | |
| 474 | + return type; | |
| 475 | + } | |
| 476 | + | |
| 477 | + /** Encodes the input vale of linear PCM, A-law or u-law data sl and returns | |
| 478 | + * the resulting code. -1 is returned for unknown input coding value. */ | |
| 479 | + public abstract int encode(int sl, int in_coding); | |
| 480 | + | |
| 481 | + | |
| 482 | + /** Encodes the input chunk in_buff of linear PCM, A-law or u-law data and returns | |
| 483 | + * the G726 encoded chuck into out_buff. <br> | |
| 484 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 485 | + * in_coding value. */ | |
| 486 | + public abstract int encode(byte[] in_buff, int in_offset, int in_len, int in_coding, byte[] out_buff, int out_offset); | |
| 487 | + | |
| 488 | + | |
| 489 | + /** Decodes a 4-bit code of G.72x encoded data of i and | |
| 490 | + * returns the resulting linear PCM, A-law or u-law value. | |
| 491 | + * return -1 for unknown out_coding value. */ | |
| 492 | + public abstract int decode(int i, int out_coding); | |
| 493 | + | |
| 494 | + | |
| 495 | + /** Decodes the input chunk in_buff of G726 encoded data and returns | |
| 496 | + * the linear PCM, A-law or u-law chunk into out_buff. <br> | |
| 497 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 498 | + * out_coding value. */ | |
| 499 | + public abstract int decode(byte[] in_buff, int in_offset, int in_len, int out_coding, byte[] out_buff, int out_offset); | |
| 500 | + | |
| 501 | +} | |
| 0 | 502 | \ No newline at end of file | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/g726/G726State.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/g726/G726State.java | |
| 1 | +package cn.org.hentai.jtt1078.codec.g726; | |
| 2 | + | |
| 3 | +public class G726State { | |
| 4 | + | |
| 5 | + /** Locked or steady state step size multiplier. */ | |
| 6 | + /*long*/int yl; | |
| 7 | + /** Unlocked or non-steady state step size multiplier. */ | |
| 8 | + /*short*/int yu; | |
| 9 | + /** Short term energy estimate. */ | |
| 10 | + /*short*/int dms; | |
| 11 | + /** Long term energy estimate. */ | |
| 12 | + /*short*/int dml; | |
| 13 | + /** Linear weighting coefficient of 'yl' and 'yu'. */ | |
| 14 | + /*short*/int ap; | |
| 15 | + | |
| 16 | + /** Coefficients of pole portion of prediction filter. */ | |
| 17 | + /*short*/int[] a; | |
| 18 | + /** Coefficients of zero portion of prediction filter. */ | |
| 19 | + /*short*/int[] b; | |
| 20 | + /** Signs of previous two samples of a partially | |
| 21 | + * reconstructed signal. */ | |
| 22 | + /*short*/int[] pk; | |
| 23 | + /** Previous 6 samples of the quantized difference | |
| 24 | + * signal represented in an internal floating point | |
| 25 | + * format. */ | |
| 26 | + /*short*/int[] dq; | |
| 27 | + /** Previous 2 samples of the quantized difference | |
| 28 | + * signal represented in an internal floating point | |
| 29 | + * format. */ | |
| 30 | + /*short*/int[] sr; | |
| 31 | + /* delayed tone detect, new in 1988 version */ | |
| 32 | + /*char*/int td; | |
| 33 | + | |
| 34 | + | |
| 35 | + /** The first 15 values, powers of 2. */ | |
| 36 | + private static final /*short*/int[] power2 = { 1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000 }; | |
| 37 | + | |
| 38 | + | |
| 39 | + /** Quantizes the input val against the table of size short integers. | |
| 40 | + * It returns i if table[i-1]<=val<table[i]. | |
| 41 | + * <p> | |
| 42 | + * Using linear search for simple coding. */ | |
| 43 | + private static int quan(int val, /*short*/int[] table, int size) { | |
| 44 | + | |
| 45 | + int i; | |
| 46 | + for (i=0; i<size; i++) if (val<table[i]) break; | |
| 47 | + return i; | |
| 48 | + } | |
| 49 | + | |
| 50 | + | |
| 51 | + /** returns the integer product of the 14-bit integer "an" and | |
| 52 | + * "floating point" representation (4-bit exponent, 6-bit mantessa) "srn". */ | |
| 53 | + private static int fmult(int an, int srn) { | |
| 54 | + | |
| 55 | + /*short*/int anmag=(an>0)? an : ((-an)&0x1FFF); | |
| 56 | + /*short*/int anexp=quan(anmag, power2, 15)-6; | |
| 57 | + /*short*/int anmant=(anmag==0)? 32 : | |
| 58 | + (anexp >= 0)? anmag>>anexp : anmag<<-anexp; | |
| 59 | + /*short*/int wanexp=anexp + ((srn>>6)&0xF) - 13; | |
| 60 | + | |
| 61 | + /*short*/int wanmant=(anmant*(srn&077) + 0x30)>>4; | |
| 62 | + /*short*/int retval=(wanexp>=0)? ((wanmant<<wanexp)&0x7FFF) : (wanmant>>-wanexp); | |
| 63 | + | |
| 64 | + return (((an^srn)<0)? -retval : retval); | |
| 65 | + } | |
| 66 | + | |
| 67 | + | |
| 68 | + /** Creates a new G726State. */ | |
| 69 | + public G726State() { | |
| 70 | + a=new /*short*/int[2]; | |
| 71 | + b=new /*short*/int[6]; | |
| 72 | + pk=new /*short*/int[2]; | |
| 73 | + dq=new /*short*/int[6]; | |
| 74 | + sr=new /*short*/int[2]; | |
| 75 | + init(); | |
| 76 | + } | |
| 77 | + | |
| 78 | + /** This routine initializes and/or resets the G726State 'state'. <br> | |
| 79 | + * All the initial state values are specified in the CCITT G.721 document. */ | |
| 80 | + private void init() { | |
| 81 | + yl=34816; | |
| 82 | + yu=544; | |
| 83 | + dms=0; | |
| 84 | + dml=0; | |
| 85 | + ap=0; | |
| 86 | + for (int cnta=0; cnta<2; cnta++) { | |
| 87 | + a[cnta]=0; | |
| 88 | + pk[cnta]=0; | |
| 89 | + sr[cnta]=32; | |
| 90 | + } | |
| 91 | + for (int cnta=0; cnta<6; cnta++) { | |
| 92 | + b[cnta]=0; | |
| 93 | + dq[cnta]=32; | |
| 94 | + } | |
| 95 | + td=0; | |
| 96 | + } | |
| 97 | + | |
| 98 | + /** computes the estimated signal from 6-zero predictor. */ | |
| 99 | + public int predictor_zero() { | |
| 100 | + | |
| 101 | + int sezi=fmult(b[0]>>2, dq[0]); | |
| 102 | + /* ACCUM */ | |
| 103 | + for (int i=1; i<6; i++) sezi+=fmult(b[i]>>2,dq[i]); | |
| 104 | + return sezi; | |
| 105 | + } | |
| 106 | + | |
| 107 | + | |
| 108 | + /** computes the estimated signal from 2-pole predictor. */ | |
| 109 | + public int predictor_pole() { | |
| 110 | + | |
| 111 | + return (fmult(a[1]>>2,sr[1]) + fmult(a[0]>>2,sr[0])); | |
| 112 | + } | |
| 113 | + | |
| 114 | + | |
| 115 | + /** computes the quantization step size of the adaptive quantizer. */ | |
| 116 | + public int step_size() { | |
| 117 | + | |
| 118 | + if (ap>=256) return (yu); | |
| 119 | + else { | |
| 120 | + int y=yl>>6; | |
| 121 | + int dif=yu-y; | |
| 122 | + int al=ap>>2; | |
| 123 | + if (dif>0) y+=(dif * al)>>6; | |
| 124 | + else | |
| 125 | + if (dif<0) y+=(dif * al+0x3F)>>6; | |
| 126 | + return y; | |
| 127 | + } | |
| 128 | + } | |
| 129 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/g726/G726_16.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/g726/G726_16.java | |
| 1 | +package cn.org.hentai.jtt1078.codec.g726; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.codec.G711Codec; | |
| 4 | +import cn.org.hentai.jtt1078.codec.G711UCodec; | |
| 5 | + | |
| 6 | +/** G726_16 encoder and decoder. | |
| 7 | + * <p> | |
| 8 | + * These routines comprise an implementation of the CCITT G.726 16kbps | |
| 9 | + * ADPCM coding algorithm. Essentially, this implementation is identical to | |
| 10 | + * the bit level description except for a few deviations which | |
| 11 | + * take advantage of workstation attributes, such as hardware 2's | |
| 12 | + * complement arithmetic. | |
| 13 | + * <p> | |
| 14 | + * The deviation from the bit level specification (lookup tables), | |
| 15 | + * preserves the bit level performance specifications. | |
| 16 | + * <p> | |
| 17 | + * As outlined in the G.723 Recommendation, the algorithm is broken | |
| 18 | + * down into modules. Each section of code below is preceded by | |
| 19 | + * the name of the module which it is implementing. | |
| 20 | + * <p> | |
| 21 | + * This implementation is based on the ANSI-C language reference implementations | |
| 22 | + * of the CCITT (International Telegraph and Telephone Consultative Committee) | |
| 23 | + * G.711, G.721 and G.723 voice compressions, provided by Sun Microsystems, Inc. | |
| 24 | + * <p> | |
| 25 | + * Acknowledgement to Sun Microsystems, Inc. for having released the original | |
| 26 | + * ANSI-C source code to the public domain. | |
| 27 | + */ | |
| 28 | +public class G726_16 extends G726 { | |
| 29 | + | |
| 30 | + // ##### C-to-Java conversion: ##### | |
| 31 | + // short becomes int | |
| 32 | + // char becomes int | |
| 33 | + // unsigned char becomes int | |
| 34 | + | |
| 35 | + | |
| 36 | + // *************************** STATIC *************************** | |
| 37 | + | |
| 38 | + /* | |
| 39 | + * Maps G723_16 code word to ructeconstructed scale factor normalized log | |
| 40 | + * magnitude values. | |
| 41 | + */ | |
| 42 | + static /*short*/int[] _dqlntab={116, 365, 365, 116}; | |
| 43 | + | |
| 44 | + /* Maps G723_16 code word to log of scale factor multiplier. */ | |
| 45 | + static /*short*/int[] _witab={-704, 14048, 14048, -704}; | |
| 46 | + | |
| 47 | + /* | |
| 48 | + * Maps G723_16 code words to a set of values whose long and short | |
| 49 | + * term averages are computed and then compared to give an indication | |
| 50 | + * how stationary (steady state) the signal is. | |
| 51 | + */ | |
| 52 | + static /*short*/int[] _fitab={ 0x000, 0xE00, 0xE00, 0x000}; | |
| 53 | + | |
| 54 | + static /*short*/int[] qtab_723_16={261}; | |
| 55 | + | |
| 56 | + /** Encodes a 16-bit linear PCM, A-law or u-law input sample and retuens | |
| 57 | + * the resulting 5-bit CCITT G726 16kbps code. | |
| 58 | + * Returns -1 if the input coding value is invalid. */ | |
| 59 | + public static int encode(int sl, int in_coding, G726State state) { | |
| 60 | + | |
| 61 | + /*short*/int sei, sezi, se, sez; /* ACCUM */ | |
| 62 | + /*short*/int d; /* SUBTA */ | |
| 63 | + /*short*/int y; /* MIX */ | |
| 64 | + /*short*/int sr; /* ADDB */ | |
| 65 | + /*short*/int dqsez; /* ADDC */ | |
| 66 | + /*short*/int dq, i; | |
| 67 | + | |
| 68 | + switch (in_coding) { | |
| 69 | + /* linearize input sample to 14-bit PCM */ | |
| 70 | + case AUDIO_ENCODING_ALAW: | |
| 71 | + sl= G711Codec.alaw2linear((byte) sl) >> 2; | |
| 72 | + break; | |
| 73 | + case AUDIO_ENCODING_ULAW: | |
| 74 | + sl= G711UCodec.ulaw2linear((byte)sl) >> 2; | |
| 75 | + break; | |
| 76 | + case AUDIO_ENCODING_LINEAR: | |
| 77 | + sl >>= 2; /* sl of 14-bit dynamic range */ | |
| 78 | + break; | |
| 79 | + default: | |
| 80 | + return (-1); | |
| 81 | + } | |
| 82 | + | |
| 83 | + sezi=state.predictor_zero(); | |
| 84 | + sez=sezi >> 1; | |
| 85 | + sei=sezi+state.predictor_pole(); | |
| 86 | + se=sei >> 1; /* se=estimated signal */ | |
| 87 | + | |
| 88 | + d=sl-se; /* d=estimation difference */ | |
| 89 | + | |
| 90 | + /* quantize prediction difference */ | |
| 91 | + y=state.step_size(); /* adaptive quantizer step size */ | |
| 92 | + i=quantize(d, y, qtab_723_16, 1); /* i=ADPCM code */ | |
| 93 | + | |
| 94 | + dq=reconstruct(i & 0x02, _dqlntab[i], y); /* quantized diff */ | |
| 95 | + | |
| 96 | + sr=(dq<0)? se-(dq & 0x3FFF) : se+dq; /* reconstructed signal */ | |
| 97 | + | |
| 98 | + dqsez=sr+sez-se; /* dqsez=pole prediction diff. */ | |
| 99 | + | |
| 100 | + update(2, y, _witab[i], _fitab[i], dq, sr, dqsez, state); | |
| 101 | + | |
| 102 | + return (i); | |
| 103 | + } | |
| 104 | + | |
| 105 | + | |
| 106 | + /** Decodes a 5-bit CCITT G.726 40kbps code and returns | |
| 107 | + * the resulting 16-bit linear PCM, A-law or u-law sample value. | |
| 108 | + * -1 is returned if the output coding is unknown. */ | |
| 109 | + public static int decode(int i, int out_coding, G726State state) { | |
| 110 | + | |
| 111 | + /*short*/int sezi, sei, sez, se; /* ACCUM */ | |
| 112 | + /*short*/int y, dif; /* MIX */ | |
| 113 | + /*short*/int sr; /* ADDB */ | |
| 114 | + /*short*/int dq; | |
| 115 | + /*short*/int dqsez; | |
| 116 | + | |
| 117 | + i &= 0x03; /* mask to get proper bits */ | |
| 118 | + sezi=state.predictor_zero(); | |
| 119 | + sez=sezi >> 1; | |
| 120 | + sei=sezi+state.predictor_pole(); | |
| 121 | + se=sei >> 1; /* se=estimated signal */ | |
| 122 | + | |
| 123 | + y=state.step_size(); /* adaptive quantizer step size */ | |
| 124 | + dq=reconstruct(i & 0x02, _dqlntab[i], y); /* estimation diff. */ | |
| 125 | + | |
| 126 | + sr=(dq<0)? (se-(dq & 0x3FFF)) : (se+dq); /* reconst. signal */ | |
| 127 | + | |
| 128 | + dqsez=sr-se+sez; /* pole prediction diff. */ | |
| 129 | + | |
| 130 | + update(2, y, _witab[i], _fitab[i], dq, sr, dqsez, state); | |
| 131 | + | |
| 132 | + switch (out_coding) { | |
| 133 | + | |
| 134 | + case AUDIO_ENCODING_ALAW: | |
| 135 | + return (tandem_adjust_alaw(sr, se, y, i, 0x02, qtab_723_16)); | |
| 136 | + case AUDIO_ENCODING_ULAW: | |
| 137 | + return (tandem_adjust_ulaw(sr, se, y, i, 0x02, qtab_723_16)); | |
| 138 | + case AUDIO_ENCODING_LINEAR: | |
| 139 | + return (sr << 2); /* sr was of 14-bit dynamic range */ | |
| 140 | + default: | |
| 141 | + return (-1); | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + | |
| 146 | + /** Encodes the input chunk in_buff of linear PCM, A-law or u-law data and returns | |
| 147 | + * the G726_16 encoded chuck into out_buff. <br> | |
| 148 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 149 | + * in_coding value. */ | |
| 150 | + public static int encode(byte[] in_buff, int in_offset, int in_len, int in_coding, byte[] out_buff, int out_offset, G726State state) { | |
| 151 | + | |
| 152 | + if (in_coding==AUDIO_ENCODING_ALAW || in_coding==AUDIO_ENCODING_ULAW) { | |
| 153 | + | |
| 154 | + int len_div_8=in_len/8; | |
| 155 | + for (int i=0; i<len_div_8; i++) { | |
| 156 | + long value8=0; | |
| 157 | + int in_index=in_offset+i*8; | |
| 158 | + for (int j=0; j<8; j++) { | |
| 159 | + int in_value=unsignedInt(in_buff[in_index+j]); | |
| 160 | + int out_value=encode(in_value,in_coding,state); | |
| 161 | + value8+=((long)out_value)<<(2*(7-j)); | |
| 162 | + } | |
| 163 | + int out_index=out_offset+i*2; | |
| 164 | + for (int k=0; k<2; k++) { | |
| 165 | + out_buff[out_index+k]=(byte)(value8>>(8*(1-k))); | |
| 166 | + } | |
| 167 | + } | |
| 168 | + return len_div_8*2; | |
| 169 | + } | |
| 170 | + else | |
| 171 | + if (in_coding==AUDIO_ENCODING_LINEAR) { | |
| 172 | + | |
| 173 | + int len_div_16=in_len/16; | |
| 174 | + for (int i=0; i<len_div_16; i++) { | |
| 175 | + long value16=0; | |
| 176 | + int in_index=in_offset+i*16; | |
| 177 | + for (int j=0; j<8; j++) { | |
| 178 | + int j2=j*2; | |
| 179 | + int in_value=signedIntLittleEndian(in_buff[in_index+j2+1],in_buff[in_index+j2]); | |
| 180 | + int out_value=encode(in_value,in_coding,state); | |
| 181 | + value16+=((long)out_value)<<(2*(7-j)); | |
| 182 | + } | |
| 183 | + int out_index=out_offset+i*2; | |
| 184 | + for (int k=0; k<2; k++) { | |
| 185 | + out_buff[out_index+k]=(byte)(value16>>(8*(1-k))); | |
| 186 | + } | |
| 187 | + } | |
| 188 | + return len_div_16*2; | |
| 189 | + } | |
| 190 | + else return -1; | |
| 191 | + } | |
| 192 | + | |
| 193 | + | |
| 194 | + /** Decodes the input chunk in_buff of G726_16 encoded data and returns | |
| 195 | + * the linear PCM, A-law or u-law chunk into out_buff. <br> | |
| 196 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 197 | + * out_coding value. */ | |
| 198 | + public static int decode(byte[] in_buff, int in_offset, int in_len, int out_coding, byte[] out_buff, int out_offset, G726State state) { | |
| 199 | + | |
| 200 | + if (out_coding==AUDIO_ENCODING_ALAW || out_coding==AUDIO_ENCODING_ULAW) { | |
| 201 | + | |
| 202 | + int len_div_2=in_len/2; | |
| 203 | + for (int i=0; i<len_div_2; i++) { | |
| 204 | + int value8=0; | |
| 205 | + int in_index=in_offset+i*2; | |
| 206 | + for (int j=0; j<2; j++) { | |
| 207 | + value8+=unsignedInt(in_buff[in_index+j])<<(8*(1-j)); | |
| 208 | + } | |
| 209 | + int out_index=out_offset+i*8; | |
| 210 | + for (int k=0; k<8; k++) { | |
| 211 | + int in_value=(value8>>(2*(7-k)))&0x3; | |
| 212 | + int out_value=decode(in_value,out_coding,state); | |
| 213 | + out_buff[out_index+k]=(byte)out_value; | |
| 214 | + } | |
| 215 | + } | |
| 216 | + return len_div_2*8; | |
| 217 | + } | |
| 218 | + else | |
| 219 | + if (out_coding==AUDIO_ENCODING_LINEAR) { | |
| 220 | + | |
| 221 | + int len_div_2=in_len/2; | |
| 222 | + for (int i=0; i<len_div_2; i++) { | |
| 223 | + int value16=0; | |
| 224 | + int in_index=in_offset+i*2; | |
| 225 | + for (int j=0; j<2; j++) { | |
| 226 | + value16+=unsignedInt(in_buff[in_index+j])<<(8*(1-j)); | |
| 227 | + } | |
| 228 | + int out_index=out_offset+i*16; | |
| 229 | + for (int k=0; k<8; k++) { | |
| 230 | + int k2=k*2; | |
| 231 | + int in_value=(value16>>(2*(7-k)))&0x3; | |
| 232 | + //int out_value=G711.ulaw2linear(decode(in_value,AUDIO_ENCODING_ULAW,state)); | |
| 233 | + int out_value=decode(in_value,out_coding,state); | |
| 234 | + out_buff[out_index+k2]=(byte)(out_value&0xFF); | |
| 235 | + out_buff[out_index+k2+1]=(byte)(out_value>>8); | |
| 236 | + } | |
| 237 | + } | |
| 238 | + return len_div_2*16; | |
| 239 | + } | |
| 240 | + else return -1; | |
| 241 | + } | |
| 242 | + | |
| 243 | + | |
| 244 | + // ************************* NON-STATIC ************************* | |
| 245 | + | |
| 246 | + /** Creates a new G726_16 processor, that can be used to encode from or decode do PCM audio data. */ | |
| 247 | + public G726_16() { | |
| 248 | + super(16000); | |
| 249 | + } | |
| 250 | + | |
| 251 | + | |
| 252 | + /** Encodes a 16-bit linear PCM, A-law or u-law input sample and retuens | |
| 253 | + * the resulting 5-bit CCITT G.726 40kbps code. | |
| 254 | + * Returns -1 if the input coding value is invalid. */ | |
| 255 | + public int encode(int sl, int in_coding) { | |
| 256 | + return encode(sl,in_coding,state); | |
| 257 | + } | |
| 258 | + | |
| 259 | + | |
| 260 | + /** Encodes the input chunk in_buff of linear PCM, A-law or u-law data and returns | |
| 261 | + * the G726_16 encoded chuck into out_buff. <br> | |
| 262 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 263 | + * in_coding value. */ | |
| 264 | + public int encode(byte[] in_buff, int in_offset, int in_len, int in_coding, byte[] out_buff, int out_offset) { | |
| 265 | + return encode(in_buff,in_offset,in_len,in_coding,out_buff,out_offset,state); | |
| 266 | + } | |
| 267 | + | |
| 268 | + | |
| 269 | + /** Decodes a 5-bit CCITT G.726 40kbps code and returns | |
| 270 | + * the resulting 16-bit linear PCM, A-law or u-law sample value. | |
| 271 | + * -1 is returned if the output coding is unknown. */ | |
| 272 | + public int decode(int i, int out_coding) { | |
| 273 | + return decode(i,out_coding,state); | |
| 274 | + } | |
| 275 | + | |
| 276 | + | |
| 277 | + /** Decodes the input chunk in_buff of G726_16 encoded data and returns | |
| 278 | + * the linear PCM, A-law or u-law chunk into out_buff. <br> | |
| 279 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 280 | + * out_coding value. */ | |
| 281 | + public int decode(byte[] in_buff, int in_offset, int in_len, int out_coding, byte[] out_buff, int out_offset) { | |
| 282 | + return decode(in_buff,in_offset,in_len,out_coding,out_buff,out_offset,state); | |
| 283 | + } | |
| 284 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/g726/G726_24.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/g726/G726_24.java | |
| 1 | +package cn.org.hentai.jtt1078.codec.g726; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.codec.G711Codec; | |
| 4 | +import cn.org.hentai.jtt1078.codec.G711UCodec; | |
| 5 | + | |
| 6 | +/** G726_24 encoder and decoder. | |
| 7 | + * <p> | |
| 8 | + * These routines comprise an implementation of the CCITT G.726 24kbps | |
| 9 | + * ADPCM coding algorithm. Essentially, this implementation is identical to | |
| 10 | + * the bit level description except for a few deviations which take advantage | |
| 11 | + * of workstation attributes, such as hardware 2's complement arithmetic. | |
| 12 | + * <p> | |
| 13 | + * This implementation is based on the ANSI-C language reference implementations | |
| 14 | + * of the CCITT (International Telegraph and Telephone Consultative Committee) | |
| 15 | + * G.711, G.721 and G.723 voice compressions, provided by Sun Microsystems, Inc. | |
| 16 | + * <p> | |
| 17 | + * Acknowledgement to Sun Microsystems, Inc. for having released the original | |
| 18 | + * ANSI-C source code to the public domain. | |
| 19 | + */ | |
| 20 | +public class G726_24 extends G726 { | |
| 21 | + | |
| 22 | + // ##### C-to-Java conversion: ##### | |
| 23 | + // short becomes int | |
| 24 | + // char becomes int | |
| 25 | + // unsigned char becomes int | |
| 26 | + | |
| 27 | + | |
| 28 | + // *************************** STATIC *************************** | |
| 29 | + | |
| 30 | + /* | |
| 31 | + * Maps G726_24 code word to reconstructed scale factor normalized log | |
| 32 | + * magnitude values. | |
| 33 | + */ | |
| 34 | + static /*short*/int[] _dqlntab={-2048, 135, 273, 373, 373, 273, 135, -2048}; | |
| 35 | + | |
| 36 | + /* Maps G726_24 code word to log of scale factor multiplier. */ | |
| 37 | + static /*short*/int[] _witab={-128, 960, 4384, 18624, 18624, 4384, 960, -128}; | |
| 38 | + | |
| 39 | + /* | |
| 40 | + * Maps G726_24 code words to a set of values whose long and short | |
| 41 | + * term averages are computed and then compared to give an indication | |
| 42 | + * how stationary (steady state) the signal is. | |
| 43 | + */ | |
| 44 | + static /*short*/int[] _fitab={0, 0x200, 0x400, 0xE00, 0xE00, 0x400, 0x200, 0}; | |
| 45 | + | |
| 46 | + static /*short*/int[] qtab_723_24={8, 218, 331}; | |
| 47 | + | |
| 48 | + /** Encodes a linear PCM, A-law or u-law input sample and returns its 3-bit code. | |
| 49 | + * Returns -1 if invalid input coding value. */ | |
| 50 | + public static int encode(int sl, int in_coding, G726State state) { | |
| 51 | + | |
| 52 | + /*short*/int sei, sezi, se, sez; /* ACCUM */ | |
| 53 | + /*short*/int d; /* SUBTA */ | |
| 54 | + /*short*/int y; /* MIX */ | |
| 55 | + /*short*/int sr; /* ADDB */ | |
| 56 | + /*short*/int dqsez; /* ADDC */ | |
| 57 | + /*short*/int dq, i; | |
| 58 | + | |
| 59 | + switch (in_coding) { | |
| 60 | + /* linearize input sample to 14-bit PCM */ | |
| 61 | + case AUDIO_ENCODING_ALAW: | |
| 62 | + sl= G711Codec.alaw2linear((byte)sl) >> 2; | |
| 63 | + break; | |
| 64 | + case AUDIO_ENCODING_ULAW: | |
| 65 | + sl= G711UCodec.ulaw2linear((byte)sl) >> 2; | |
| 66 | + break; | |
| 67 | + case AUDIO_ENCODING_LINEAR: | |
| 68 | + sl >>= 2; /* sl of 14-bit dynamic range */ | |
| 69 | + break; | |
| 70 | + default: | |
| 71 | + return (-1); | |
| 72 | + } | |
| 73 | + | |
| 74 | + sezi=state.predictor_zero(); | |
| 75 | + sez=sezi >> 1; | |
| 76 | + sei=sezi+state.predictor_pole(); | |
| 77 | + se=sei >> 1; /* se=estimated signal */ | |
| 78 | + | |
| 79 | + d=sl-se; /* d=estimation diff. */ | |
| 80 | + | |
| 81 | + /* quantize prediction difference d */ | |
| 82 | + y=state.step_size(); /* quantizer step size */ | |
| 83 | + i=quantize(d, y, qtab_723_24, 3); /* i=ADPCM code */ | |
| 84 | + dq=reconstruct(i & 4, _dqlntab[i], y); /* quantized diff. */ | |
| 85 | + | |
| 86 | + sr=(dq<0)? se-(dq & 0x3FFF) : se+dq; /* reconstructed signal */ | |
| 87 | + | |
| 88 | + dqsez=sr+sez-se; /* pole prediction diff. */ | |
| 89 | + | |
| 90 | + update(3, y, _witab[i], _fitab[i], dq, sr, dqsez, state); | |
| 91 | + | |
| 92 | + return (i); | |
| 93 | + } | |
| 94 | + | |
| 95 | + | |
| 96 | + /** Decodes a 3-bit CCITT G.726 24kbps ADPCM code and returns | |
| 97 | + * the resulting 16-bit linear PCM, A-law or u-law sample value. | |
| 98 | + * -1 is returned if the output coding is unknown. */ | |
| 99 | + public static int decode(int i, int out_coding, G726State state) { | |
| 100 | + | |
| 101 | + /*short*/int sezi, sei, sez, se; /* ACCUM */ | |
| 102 | + /*short*/int y; /* MIX */ | |
| 103 | + /*short*/int sr; /* ADDB */ | |
| 104 | + /*short*/int dq; | |
| 105 | + /*short*/int dqsez; | |
| 106 | + | |
| 107 | + i &= 0x07; /* mask to get proper bits */ | |
| 108 | + sezi=state.predictor_zero(); | |
| 109 | + sez=sezi >> 1; | |
| 110 | + sei=sezi+state.predictor_pole(); | |
| 111 | + se=sei >> 1; /* se=estimated signal */ | |
| 112 | + | |
| 113 | + y=state.step_size(); /* adaptive quantizer step size */ | |
| 114 | + dq=reconstruct(i & 0x04, _dqlntab[i], y); /* unquantize pred diff */ | |
| 115 | + | |
| 116 | + sr=(dq<0)? (se-(dq & 0x3FFF)) : (se+dq); /* reconst. signal */ | |
| 117 | + | |
| 118 | + dqsez=sr-se+sez; /* pole prediction diff. */ | |
| 119 | + | |
| 120 | + update(3, y, _witab[i], _fitab[i], dq, sr, dqsez, state); | |
| 121 | + | |
| 122 | + switch (out_coding) { | |
| 123 | + | |
| 124 | + case AUDIO_ENCODING_ALAW: | |
| 125 | + return (tandem_adjust_alaw(sr, se, y, i, 4, qtab_723_24)); | |
| 126 | + case AUDIO_ENCODING_ULAW: | |
| 127 | + return (tandem_adjust_ulaw(sr, se, y, i, 4, qtab_723_24)); | |
| 128 | + case AUDIO_ENCODING_LINEAR: | |
| 129 | + return (sr << 2); /* sr was of 14-bit dynamic range */ | |
| 130 | + default: | |
| 131 | + return (-1); | |
| 132 | + } | |
| 133 | + } | |
| 134 | + | |
| 135 | + | |
| 136 | + /** Encodes the input chunk in_buff of linear PCM, A-law or u-law data and returns | |
| 137 | + * the G726_24 encoded chuck into out_buff. <br> | |
| 138 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 139 | + * in_coding value. */ | |
| 140 | + public static int encode(byte[] in_buff, int in_offset, int in_len, int in_coding, byte[] out_buff, int out_offset, G726State state) { | |
| 141 | + | |
| 142 | + if (in_coding==AUDIO_ENCODING_ALAW || in_coding==AUDIO_ENCODING_ULAW) { | |
| 143 | + | |
| 144 | + int len_div_8=in_len/8; | |
| 145 | + for (int i=0; i<len_div_8; i++) { | |
| 146 | + int value8=0; | |
| 147 | + int i8=i*8; | |
| 148 | + for (int j=0; j<8; j++) { | |
| 149 | + int in_value=unsignedInt(in_buff[in_offset+i8+j]); | |
| 150 | + int out_value=encode(in_value,in_coding,state); | |
| 151 | + value8+=out_value<<(3*(7-j)); | |
| 152 | + } | |
| 153 | + int index=out_offset+i*3; | |
| 154 | + for (int k=0; k<3; k++) { | |
| 155 | + out_buff[index+k]=(byte)(value8>>(8*(2-k))); | |
| 156 | + } | |
| 157 | + } | |
| 158 | + return len_div_8*3; | |
| 159 | + } | |
| 160 | + else | |
| 161 | + if (in_coding==AUDIO_ENCODING_LINEAR) { | |
| 162 | + | |
| 163 | + int len_div_16=in_len/16; | |
| 164 | + for (int i=0; i<len_div_16; i++) { | |
| 165 | + int value16=0; | |
| 166 | + int in_index=in_offset+i*16; | |
| 167 | + for (int j=0; j<8; j++) { | |
| 168 | + int j2=j*2; | |
| 169 | + int in_value=signedIntLittleEndian(in_buff[in_index+j2+1],in_buff[in_index+j2]); | |
| 170 | + //int out_value=encode(G711.linear2ulaw(in_value),AUDIO_ENCODING_ULAW,state); | |
| 171 | + int out_value=encode(in_value,in_coding,state); | |
| 172 | + value16+=out_value<<(3*(7-j)); | |
| 173 | + } | |
| 174 | + int out_index=out_offset+i*3; | |
| 175 | + for (int k=0; k<3; k++) { | |
| 176 | + out_buff[out_index+k]=(byte)(value16>>(8*(2-k))); | |
| 177 | + } | |
| 178 | + } | |
| 179 | + return len_div_16*3; | |
| 180 | + } | |
| 181 | + else return -1; | |
| 182 | + } | |
| 183 | + | |
| 184 | + | |
| 185 | + /** Decodes the input chunk in_buff of G726_24 encoded data and returns | |
| 186 | + * the linear PCM, A-law or u-law chunk into out_buff. <br> | |
| 187 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 188 | + * out_coding value. */ | |
| 189 | + public static int decode(byte[] in_buff, int in_offset, int in_len, int out_coding, byte[] out_buff, int out_offset, G726State state) { | |
| 190 | + | |
| 191 | + if (out_coding==AUDIO_ENCODING_ALAW || out_coding==AUDIO_ENCODING_ULAW) { | |
| 192 | + | |
| 193 | + int len_div_3=in_len/3; | |
| 194 | + for (int i=0; i<len_div_3; i++) { | |
| 195 | + int value8=0; | |
| 196 | + int in_index=in_offset+i*3; | |
| 197 | + for (int j=0; j<3; j++) { | |
| 198 | + value8+=unsignedInt(in_buff[in_index+j])<<(8*(2-j)); | |
| 199 | + } | |
| 200 | + int out_index=out_offset+i*8; | |
| 201 | + for (int k=0; k<8; k++) { | |
| 202 | + int in_value=(value8>>(3*(7-k)))&0x7; | |
| 203 | + int out_value=decode(in_value,out_coding,state); | |
| 204 | + out_buff[out_index+k]=(byte)out_value; | |
| 205 | + } | |
| 206 | + } | |
| 207 | + return len_div_3*8; | |
| 208 | + } | |
| 209 | + else | |
| 210 | + if (out_coding==AUDIO_ENCODING_LINEAR) { | |
| 211 | + | |
| 212 | + int len_div_3=in_len/3; | |
| 213 | + for (int i=0; i<len_div_3; i++) { | |
| 214 | + int value16=0; | |
| 215 | + int in_index=in_offset+i*3; | |
| 216 | + for (int j=0; j<3; j++) { | |
| 217 | + value16+=unsignedInt(in_buff[in_index+j])<<(8*(2-j)); | |
| 218 | + } | |
| 219 | + int out_index=out_offset+i*16; | |
| 220 | + for (int k=0; k<8; k++) { | |
| 221 | + int k2=k*2; | |
| 222 | + int in_value=(value16>>(3*(7-k)))&0x7; | |
| 223 | + //int out_value=G711.ulaw2linear(decode(in_value,AUDIO_ENCODING_ULAW,state)); | |
| 224 | + int out_value=decode(in_value,out_coding,state); | |
| 225 | + out_buff[out_index+k2]=(byte)(out_value&0xFF); | |
| 226 | + out_buff[out_index+k2+1]=(byte)(out_value>>8); | |
| 227 | + } | |
| 228 | + } | |
| 229 | + return len_div_3*16; | |
| 230 | + } | |
| 231 | + else return -1; | |
| 232 | + } | |
| 233 | + | |
| 234 | + | |
| 235 | + // ************************* NON-STATIC ************************* | |
| 236 | + | |
| 237 | + /** Creates a new G726_24 processor, that can be used to encode from or decode do PCM audio data. */ | |
| 238 | + public G726_24() { | |
| 239 | + super(24000); | |
| 240 | + } | |
| 241 | + | |
| 242 | + | |
| 243 | + /** Encodes a linear PCM, A-law or u-law input sample and returns its 3-bit code. | |
| 244 | + * Returns -1 if invalid input coding value. */ | |
| 245 | + public int encode(int sl, int in_coding) { | |
| 246 | + return encode(sl,in_coding,state); | |
| 247 | + } | |
| 248 | + | |
| 249 | + | |
| 250 | + /** Encodes the input chunk in_buff of linear PCM, A-law or u-law data and returns | |
| 251 | + * the G726_24 encoded chuck into out_buff. <br> | |
| 252 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 253 | + * in_coding value. */ | |
| 254 | + public int encode(byte[] in_buff, int in_offset, int in_len, int in_coding, byte[] out_buff, int out_offset) { | |
| 255 | + return encode(in_buff,in_offset,in_len,in_coding,out_buff,out_offset,state); | |
| 256 | + } | |
| 257 | + | |
| 258 | + | |
| 259 | + /** Decodes a 3-bit CCITT G.726 24kbps ADPCM code and returns | |
| 260 | + * the resulting 16-bit linear PCM, A-law or u-law sample value. | |
| 261 | + * -1 is returned if the output coding is unknown. */ | |
| 262 | + public int decode(int i, int out_coding) { | |
| 263 | + return decode(i,out_coding,state); | |
| 264 | + } | |
| 265 | + | |
| 266 | + | |
| 267 | + /** Decodes the input chunk in_buff of G726_24 encoded data and returns | |
| 268 | + * the linear PCM, A-law or u-law chunk into out_buff. <br> | |
| 269 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 270 | + * out_coding value. */ | |
| 271 | + public int decode(byte[] in_buff, int in_offset, int in_len, int out_coding, byte[] out_buff, int out_offset) { | |
| 272 | + return decode(in_buff,in_offset,in_len,out_coding,out_buff,out_offset,state); | |
| 273 | + } | |
| 274 | + | |
| 275 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/g726/G726_32.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/g726/G726_32.java | |
| 1 | + | |
| 2 | +package cn.org.hentai.jtt1078.codec.g726; | |
| 3 | + | |
| 4 | +import cn.org.hentai.jtt1078.codec.G711Codec; | |
| 5 | +import cn.org.hentai.jtt1078.codec.G711UCodec; | |
| 6 | + | |
| 7 | +/** G726_32 encoder and decoder. | |
| 8 | + * <p> | |
| 9 | + * These routines comprise an implementation of the CCITT G.726 32kbps ADPCM | |
| 10 | + * coding algorithm. Essentially, this implementation is identical to | |
| 11 | + * the bit level description except for a few deviations which | |
| 12 | + * take advantage of work station attributes, such as hardware 2's | |
| 13 | + * complement arithmetic and large memory. Specifically, certain time | |
| 14 | + * consuming operations such as multiplications are replaced | |
| 15 | + * with lookup tables and software 2's complement operations are | |
| 16 | + * replaced with hardware 2's complement. | |
| 17 | + * <p> | |
| 18 | + * The deviation from the bit level specification (lookup tables) | |
| 19 | + * preserves the bit level performance specifications. | |
| 20 | + * <p> | |
| 21 | + * As outlined in the G.726 Recommendation, the algorithm is broken | |
| 22 | + * down into modules. Each section of code below is preceded by | |
| 23 | + * the name of the module which it is implementing. | |
| 24 | + * <p> | |
| 25 | + * This implementation is based on the ANSI-C language reference implementations | |
| 26 | + * of the CCITT (International Telegraph and Telephone Consultative Committee) | |
| 27 | + * G.711, G.721 and G.723 voice compressions, provided by Sun Microsystems, Inc. | |
| 28 | + * <p> | |
| 29 | + * Acknowledgement to Sun Microsystems, Inc. for having released the original | |
| 30 | + * ANSI-C source code to the public domain. | |
| 31 | + */ | |
| 32 | +public class G726_32 extends G726 { | |
| 33 | + | |
| 34 | + // ##### C-to-Java conversion: ##### | |
| 35 | + // short becomes int | |
| 36 | + // char becomes int | |
| 37 | + // unsigned char becomes int | |
| 38 | + | |
| 39 | + | |
| 40 | + // *************************** STATIC *************************** | |
| 41 | + | |
| 42 | + static /*short*/int[] qtab_721={-124, 80, 178, 246, 300, 349, 400}; | |
| 43 | + /* | |
| 44 | + * Maps G726_32 code word to reconstructed scale factor normalized log | |
| 45 | + * magnitude values. | |
| 46 | + */ | |
| 47 | + static /*short*/int[] _dqlntab={-2048, 4, 135, 213, 273, 323, 373, 425, 425, 373, 323, 273, 213, 135, 4, -2048}; | |
| 48 | + | |
| 49 | + /* Maps G726_32 code word to log of scale factor multiplier. */ | |
| 50 | + static /*short*/int[] _witab={-12, 18, 41, 64, 112, 198, 355, 1122, 1122, 355, 198, 112, 64, 41, 18, -12}; | |
| 51 | + | |
| 52 | + /* | |
| 53 | + * Maps G726_32 code words to a set of values whose long and short | |
| 54 | + * term averages are computed and then compared to give an indication | |
| 55 | + * how stationary (steady state) the signal is. | |
| 56 | + */ | |
| 57 | + static /*short*/int[] _fitab={0, 0, 0, 0x200, 0x200, 0x200, 0x600, 0xE00, 0xE00, 0x600, 0x200, 0x200, 0x200, 0, 0, 0}; | |
| 58 | + | |
| 59 | + /** Encodes the input vale of linear PCM, A-law or u-law data sl and returns | |
| 60 | + * the resulting code. -1 is returned for unknown input coding value. */ | |
| 61 | + public static int encode(int sl, int in_coding, G726State state) { | |
| 62 | + | |
| 63 | + /*short*/int sezi, se, sez; /* ACCUM */ | |
| 64 | + /*short*/int d; /* SUBTA */ | |
| 65 | + /*short*/int sr; /* ADDB */ | |
| 66 | + /*short*/int y; /* MIX */ | |
| 67 | + /*short*/int dqsez; /* ADDC */ | |
| 68 | + /*short*/int dq, i; | |
| 69 | + | |
| 70 | + switch (in_coding) { | |
| 71 | + /* linearize input sample to 14-bit PCM */ | |
| 72 | + case AUDIO_ENCODING_ALAW: | |
| 73 | + sl= G711Codec.alaw2linear((byte)sl) >> 2; | |
| 74 | + break; | |
| 75 | + case AUDIO_ENCODING_ULAW: | |
| 76 | + sl= G711UCodec.ulaw2linear((byte)sl) >> 2; | |
| 77 | + break; | |
| 78 | + case AUDIO_ENCODING_LINEAR: | |
| 79 | + sl >>= 2; /* 14-bit dynamic range */ | |
| 80 | + break; | |
| 81 | + default: | |
| 82 | + return -1; | |
| 83 | + } | |
| 84 | + | |
| 85 | + sezi=state.predictor_zero(); | |
| 86 | + sez=sezi >> 1; | |
| 87 | + se=(sezi+state.predictor_pole()) >> 1; /* estimated signal */ | |
| 88 | + | |
| 89 | + d=sl-se; /* estimation difference */ | |
| 90 | + | |
| 91 | + /* quantize the prediction difference */ | |
| 92 | + y=state.step_size(); /* quantizer step size */ | |
| 93 | + i=quantize(d, y, qtab_721, 7); /* i=ADPCM code */ | |
| 94 | + | |
| 95 | + dq=reconstruct(i & 8, _dqlntab[i], y); /* quantized est diff */ | |
| 96 | + | |
| 97 | + sr=(dq<0)? se-(dq & 0x3FFF) : se+dq; /* reconst. signal */ | |
| 98 | + | |
| 99 | + dqsez=sr+sez-se; /* pole prediction diff. */ | |
| 100 | + | |
| 101 | + update(4, y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state); | |
| 102 | + | |
| 103 | + return i; | |
| 104 | + } | |
| 105 | + | |
| 106 | + /** Decodes a 4-bit code of G726_32 encoded data of i and | |
| 107 | + * returns the resulting linear PCM, A-law or u-law value. | |
| 108 | + * return -1 for unknown out_coding value. */ | |
| 109 | + public static int decode(int i, int out_coding, G726State state) { | |
| 110 | + | |
| 111 | + /*short*/int sezi, sei, sez, se; /* ACCUM */ | |
| 112 | + /*short*/int y; /* MIX */ | |
| 113 | + /*short*/int sr; /* ADDB */ | |
| 114 | + /*short*/int dq; | |
| 115 | + /*short*/int dqsez; | |
| 116 | + | |
| 117 | + i &= 0x0f; /* mask to get proper bits */ | |
| 118 | + sezi=state.predictor_zero(); | |
| 119 | + sez=sezi >> 1; | |
| 120 | + sei=sezi+state.predictor_pole(); | |
| 121 | + se=sei >> 1; /* se=estimated signal */ | |
| 122 | + | |
| 123 | + y=state.step_size(); /* dynamic quantizer step size */ | |
| 124 | + | |
| 125 | + dq=reconstruct(i & 0x08, _dqlntab[i], y); /* quantized diff. */ | |
| 126 | + | |
| 127 | + sr=(dq<0)? (se-(dq & 0x3FFF)) : se+dq; /* reconst. signal */ | |
| 128 | + | |
| 129 | + dqsez=sr-se+sez; /* pole prediction diff. */ | |
| 130 | + | |
| 131 | + update(4, y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state); | |
| 132 | + | |
| 133 | + switch (out_coding) { | |
| 134 | + | |
| 135 | + case AUDIO_ENCODING_ALAW: | |
| 136 | + return (tandem_adjust_alaw(sr, se, y, i, 8, qtab_721)); | |
| 137 | + case AUDIO_ENCODING_ULAW: | |
| 138 | + return (tandem_adjust_ulaw(sr, se, y, i, 8, qtab_721)); | |
| 139 | + case AUDIO_ENCODING_LINEAR: | |
| 140 | + return (sr << 2); /* sr was 14-bit dynamic range */ | |
| 141 | + default: | |
| 142 | + return -1; | |
| 143 | + } | |
| 144 | + } | |
| 145 | + | |
| 146 | + | |
| 147 | + /** Encodes the input chunk in_buff of linear PCM, A-law or u-law data and returns | |
| 148 | + * the G726_32 encoded chuck into out_buff. <br> | |
| 149 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 150 | + * in_coding value. */ | |
| 151 | + public static int encode(byte[] in_buff, int in_offset, int in_len, int in_coding, byte[] out_buff, int out_offset, G726State state) { | |
| 152 | + | |
| 153 | + if (in_coding==AUDIO_ENCODING_ALAW || in_coding==AUDIO_ENCODING_ULAW) { | |
| 154 | + | |
| 155 | + /* | |
| 156 | + for (int i=0; i<in_len; i++) { | |
| 157 | + int in_value=in_buff[in_offset+i]; | |
| 158 | + int out_value=encode(in_value,in_coding,state); | |
| 159 | + int i_div_2=i/2; | |
| 160 | + if (i_div_2*2==i) | |
| 161 | + out_buff[out_offset+i_div_2]=(byte)(out_value<<4); | |
| 162 | + else | |
| 163 | + out_buff[out_offset+i_div_2]=(byte)(out_value+unsignedInt(out_buff[out_offset+i_div_2])); | |
| 164 | + } | |
| 165 | + return in_len/2; | |
| 166 | + */ | |
| 167 | + int len_div_2=in_len/2; | |
| 168 | + for (int i=0; i<len_div_2; i++) { | |
| 169 | + int in_index=in_offset+i*2; | |
| 170 | + int in_value1=in_buff[in_index]; | |
| 171 | + int in_value2=in_buff[in_index+1]; | |
| 172 | + int out_value1=encode(in_value1,in_coding,state); | |
| 173 | + int out_value2=encode(in_value2,in_coding,state); | |
| 174 | + out_buff[out_offset+i]=(byte)((out_value1<<4) + out_value2); | |
| 175 | + } | |
| 176 | + return len_div_2; | |
| 177 | + } | |
| 178 | + else | |
| 179 | + if (in_coding==AUDIO_ENCODING_LINEAR) { | |
| 180 | + | |
| 181 | + int len_div_4=in_len/4; | |
| 182 | + for (int i=0; i<len_div_4; i++) { | |
| 183 | + int in_index=in_offset+i*4; | |
| 184 | + int in_value1=signedIntLittleEndian(in_buff[in_index+1],in_buff[in_index+0]); | |
| 185 | + int in_value2=signedIntLittleEndian(in_buff[in_index+3],in_buff[in_index+2]); | |
| 186 | + | |
| 187 | + //int out_value1=encode(G711.linear2ulaw(in_value1),AUDIO_ENCODING_ULAW,state); | |
| 188 | + //int out_value2=encode(G711.linear2ulaw(in_value2),AUDIO_ENCODING_ULAW,state); | |
| 189 | + int out_value1=encode(in_value1,in_coding,state); | |
| 190 | + int out_value2=encode(in_value2,in_coding,state); | |
| 191 | + out_buff[out_offset+i]=(byte)((out_value1<<4) + out_value2); | |
| 192 | + } | |
| 193 | + return len_div_4; | |
| 194 | + } | |
| 195 | + else return -1; | |
| 196 | + } | |
| 197 | + | |
| 198 | + | |
| 199 | + /** Decodes the input chunk in_buff of G726_32 encoded data and returns | |
| 200 | + * the linear PCM, A-law or u-law chunk into out_buff. <br> | |
| 201 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 202 | + * out_coding value. */ | |
| 203 | + public static int decode(byte[] in_buff, int in_offset, int in_len, int out_coding, byte[] out_buff, int out_offset, G726State state) { | |
| 204 | + | |
| 205 | + if (out_coding==AUDIO_ENCODING_ALAW || out_coding==AUDIO_ENCODING_ULAW) { | |
| 206 | + | |
| 207 | + /* | |
| 208 | + for (int i=0; i<in_len*2; i++) { | |
| 209 | + int in_value=unsignedInt(in_buff[in_offset+i/2]); | |
| 210 | + if ((i/2)*2==i) | |
| 211 | + in_value>>=4; | |
| 212 | + else | |
| 213 | + in_value%=0x10; | |
| 214 | + int out_value=decode(in_value,out_coding,state); | |
| 215 | + out_buff[out_offset+i]=(byte)out_value; | |
| 216 | + } | |
| 217 | + return in_len*2; | |
| 218 | + */ | |
| 219 | + for (int i=0; i<in_len; i++) { | |
| 220 | + int in_value=unsignedInt(in_buff[in_offset+i]); | |
| 221 | + int out_value1=decode(in_value>>4,out_coding,state); | |
| 222 | + int out_value2=decode(in_value&0xF,out_coding,state); | |
| 223 | + int out_index=out_offset+i*2; | |
| 224 | + out_buff[out_index]=(byte)out_value1; | |
| 225 | + out_buff[out_index+1]=(byte)out_value2; | |
| 226 | + } | |
| 227 | + return in_len*2; | |
| 228 | + } | |
| 229 | + else | |
| 230 | + if (out_coding==AUDIO_ENCODING_LINEAR) { | |
| 231 | + | |
| 232 | + for (int i=0; i<in_len; i++) { | |
| 233 | + int in_value=unsignedInt(in_buff[in_offset+i]); | |
| 234 | + //int out_value1=G711.ulaw2linear(decode(in_value>>4,AUDIO_ENCODING_ULAW,state)); | |
| 235 | + //int out_value2=G711.ulaw2linear(decode(in_value&0xF,AUDIO_ENCODING_ULAW,state)); | |
| 236 | + int out_value1=decode(in_value>>4,out_coding,state); | |
| 237 | + int out_value2=decode(in_value&0xF,out_coding,state); | |
| 238 | + int out_index=out_offset+i*4; | |
| 239 | + out_buff[out_index]=(byte)(out_value1&0xFF); | |
| 240 | + out_buff[out_index+1]=(byte)(out_value1>>8); | |
| 241 | + out_buff[out_index+2]=(byte)(out_value2&0xFF); | |
| 242 | + out_buff[out_index+3]=(byte)(out_value2>>8); | |
| 243 | + } | |
| 244 | + return in_len*4; | |
| 245 | + } | |
| 246 | + else return -1; | |
| 247 | + } | |
| 248 | + | |
| 249 | + | |
| 250 | + // ************************* NON-STATIC ************************* | |
| 251 | + | |
| 252 | + /** Creates a new G726_32 processor, that can be used to encode from or decode do PCM audio data. */ | |
| 253 | + public G726_32() { | |
| 254 | + super(32000); | |
| 255 | + } | |
| 256 | + | |
| 257 | + | |
| 258 | + /** Encodes the input vale of linear PCM, A-law or u-law data sl and returns | |
| 259 | + * the resulting code. -1 is returned for unknown input coding value. */ | |
| 260 | + public int encode(int sl, int in_coding) { | |
| 261 | + return encode(sl,in_coding,state); | |
| 262 | + } | |
| 263 | + | |
| 264 | + /** Encodes the input chunk in_buff of linear PCM, A-law or u-law data and returns | |
| 265 | + * the G726_32 encoded chuck into out_buff. <br> | |
| 266 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 267 | + * in_coding value. */ | |
| 268 | + public int encode(byte[] in_buff, int in_offset, int in_len, int in_coding, byte[] out_buff, int out_offset) { | |
| 269 | + return encode(in_buff,in_offset,in_len,in_coding,out_buff,out_offset,state); | |
| 270 | + } | |
| 271 | + | |
| 272 | + | |
| 273 | + /** Decodes a 4-bit code of G726_32 encoded data of i and | |
| 274 | + * returns the resulting linear PCM, A-law or u-law value. | |
| 275 | + * return -1 for unknown out_coding value. */ | |
| 276 | + public int decode(int i, int out_coding) { | |
| 277 | + return decode(i,out_coding,state); | |
| 278 | + } | |
| 279 | + | |
| 280 | + | |
| 281 | + /** Decodes the input chunk in_buff of G726_32 encoded data and returns | |
| 282 | + * the linear PCM, A-law or u-law chunk into out_buff. <br> | |
| 283 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 284 | + * out_coding value. */ | |
| 285 | + public int decode(byte[] in_buff, int in_offset, int in_len, int out_coding, byte[] out_buff, int out_offset) { | |
| 286 | + return decode(in_buff,in_offset,in_len,out_coding,out_buff,out_offset,state); | |
| 287 | + } | |
| 288 | + | |
| 289 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/codec/g726/G726_40.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/codec/g726/G726_40.java | |
| 1 | +package cn.org.hentai.jtt1078.codec.g726; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.codec.G711Codec; | |
| 4 | +import cn.org.hentai.jtt1078.codec.G711UCodec; | |
| 5 | + | |
| 6 | +/** G726_40 encoder and decoder. | |
| 7 | + * <p> | |
| 8 | + * These routines comprise an implementation of the CCITT G.726 40kbps | |
| 9 | + * ADPCM coding algorithm. Essentially, this implementation is identical to | |
| 10 | + * the bit level description except for a few deviations which | |
| 11 | + * take advantage of workstation attributes, such as hardware 2's | |
| 12 | + * complement arithmetic. | |
| 13 | + * <p> | |
| 14 | + * The deviation from the bit level specification (lookup tables), | |
| 15 | + * preserves the bit level performance specifications. | |
| 16 | + * <p> | |
| 17 | + * As outlined in the G.723 Recommendation, the algorithm is broken | |
| 18 | + * down into modules. Each section of code below is preceded by | |
| 19 | + * the name of the module which it is implementing. | |
| 20 | + * <p> | |
| 21 | + * This implementation is based on the ANSI-C language reference implementations | |
| 22 | + * of the CCITT (International Telegraph and Telephone Consultative Committee) | |
| 23 | + * G.711, G.721 and G.723 voice compressions, provided by Sun Microsystems, Inc. | |
| 24 | + * <p> | |
| 25 | + * Acknowledgement to Sun Microsystems, Inc. for having released the original | |
| 26 | + * ANSI-C source code to the public domain. | |
| 27 | + */ | |
| 28 | +public class G726_40 extends G726 { | |
| 29 | + | |
| 30 | + // ##### C-to-Java conversion: ##### | |
| 31 | + // short becomes int | |
| 32 | + // char becomes int | |
| 33 | + // unsigned char becomes int | |
| 34 | + | |
| 35 | + | |
| 36 | + // *************************** STATIC *************************** | |
| 37 | + | |
| 38 | + /* | |
| 39 | + * Maps G723_40 code word to ructeconstructed scale factor normalized log | |
| 40 | + * magnitude values. | |
| 41 | + */ | |
| 42 | + static /*short*/int[] _dqlntab={-2048, -66, 28, 104, 169, 224, 274, 318, 358, 395, 429, 459, 488, 514, 539, 566, 566, 539, 514, 488, 459, 429, 395, 358, 318, 274, 224, 169, 104, 28, -66, -2048}; | |
| 43 | + | |
| 44 | + /* Maps G723_40 code word to log of scale factor multiplier. */ | |
| 45 | + static /*short*/int[] _witab={448, 448, 768, 1248, 1280, 1312, 1856, 3200, 4512, 5728, 7008, 8960, 11456, 14080, 16928, 22272, 22272, 16928, 14080, 11456, 8960, 7008, 5728, 4512, 3200, 1856, 1312, 1280, 1248, 768, 448, 448}; | |
| 46 | + | |
| 47 | + /* | |
| 48 | + * Maps G723_40 code words to a set of values whose long and short | |
| 49 | + * term averages are computed and then compared to give an indication | |
| 50 | + * how stationary (steady state) the signal is. | |
| 51 | + */ | |
| 52 | + static /*short*/int[] _fitab={0, 0, 0, 0, 0, 0x200, 0x200, 0x200, 0x200, 0x200, 0x400, 0x600, 0x800, 0xA00, 0xC00, 0xC00, 0xC00, 0xC00, 0xA00, 0x800, 0x600, 0x400, 0x200, 0x200, 0x200, 0x200, 0x200, 0, 0, 0, 0, 0}; | |
| 53 | + | |
| 54 | + static /*short*/int[] qtab_723_40={-122, -16, 68, 139, 198, 250, 298, 339, 378, 413, 445, 475, 502, 528, 553}; | |
| 55 | + | |
| 56 | + /** Encodes a 16-bit linear PCM, A-law or u-law input sample and retuens | |
| 57 | + * the resulting 5-bit CCITT G726 40kbps code. | |
| 58 | + * Returns -1 if the input coding value is invalid. */ | |
| 59 | + public static int encode(int sl, int in_coding, G726State state) { | |
| 60 | + | |
| 61 | + /*short*/int sei, sezi, se, sez; /* ACCUM */ | |
| 62 | + /*short*/int d; /* SUBTA */ | |
| 63 | + /*short*/int y; /* MIX */ | |
| 64 | + /*short*/int sr; /* ADDB */ | |
| 65 | + /*short*/int dqsez; /* ADDC */ | |
| 66 | + /*short*/int dq, i; | |
| 67 | + | |
| 68 | + switch (in_coding) { | |
| 69 | + /* linearize input sample to 14-bit PCM */ | |
| 70 | + case AUDIO_ENCODING_ALAW: | |
| 71 | + sl= G711Codec.alaw2linear((byte) sl) >> 2; | |
| 72 | + break; | |
| 73 | + case AUDIO_ENCODING_ULAW: | |
| 74 | + sl= G711UCodec.ulaw2linear((byte)sl) >> 2; | |
| 75 | + break; | |
| 76 | + case AUDIO_ENCODING_LINEAR: | |
| 77 | + sl >>= 2; /* sl of 14-bit dynamic range */ | |
| 78 | + break; | |
| 79 | + default: | |
| 80 | + return (-1); | |
| 81 | + } | |
| 82 | + | |
| 83 | + sezi=state.predictor_zero(); | |
| 84 | + sez=sezi >> 1; | |
| 85 | + sei=sezi+state.predictor_pole(); | |
| 86 | + se=sei >> 1; /* se=estimated signal */ | |
| 87 | + | |
| 88 | + d=sl-se; /* d=estimation difference */ | |
| 89 | + | |
| 90 | + /* quantize prediction difference */ | |
| 91 | + y=state.step_size(); /* adaptive quantizer step size */ | |
| 92 | + i=quantize(d, y, qtab_723_40, 15); /* i=ADPCM code */ | |
| 93 | + | |
| 94 | + dq=reconstruct(i & 0x10, _dqlntab[i], y); /* quantized diff */ | |
| 95 | + | |
| 96 | + sr=(dq<0)? se-(dq & 0x7FFF) : se+dq; /* reconstructed signal */ | |
| 97 | + | |
| 98 | + dqsez=sr+sez-se; /* dqsez=pole prediction diff. */ | |
| 99 | + | |
| 100 | + update(5, y, _witab[i], _fitab[i], dq, sr, dqsez, state); | |
| 101 | + | |
| 102 | + return (i); | |
| 103 | + } | |
| 104 | + | |
| 105 | + | |
| 106 | + /** Decodes a 5-bit CCITT G.726 40kbps code and returns | |
| 107 | + * the resulting 16-bit linear PCM, A-law or u-law sample value. | |
| 108 | + * -1 is returned if the output coding is unknown. */ | |
| 109 | + public static int decode(int i, int out_coding, G726State state) { | |
| 110 | + | |
| 111 | + /*short*/int sezi, sei, sez, se; /* ACCUM */ | |
| 112 | + /*short*/int y, dif; /* MIX */ | |
| 113 | + /*short*/int sr; /* ADDB */ | |
| 114 | + /*short*/int dq; | |
| 115 | + /*short*/int dqsez; | |
| 116 | + | |
| 117 | + i &= 0x1f; /* mask to get proper bits */ | |
| 118 | + sezi=state.predictor_zero(); | |
| 119 | + sez=sezi >> 1; | |
| 120 | + sei=sezi+state.predictor_pole(); | |
| 121 | + se=sei >> 1; /* se=estimated signal */ | |
| 122 | + | |
| 123 | + y=state.step_size(); /* adaptive quantizer step size */ | |
| 124 | + dq=reconstruct(i & 0x10, _dqlntab[i], y); /* estimation diff. */ | |
| 125 | + | |
| 126 | + sr=(dq<0)? (se-(dq & 0x7FFF)) : (se+dq); /* reconst. signal */ | |
| 127 | + | |
| 128 | + dqsez=sr-se+sez; /* pole prediction diff. */ | |
| 129 | + | |
| 130 | + update(5, y, _witab[i], _fitab[i], dq, sr, dqsez, state); | |
| 131 | + | |
| 132 | + switch (out_coding) { | |
| 133 | + | |
| 134 | + case AUDIO_ENCODING_ALAW: | |
| 135 | + return (tandem_adjust_alaw(sr, se, y, i, 0x10, qtab_723_40)); | |
| 136 | + case AUDIO_ENCODING_ULAW: | |
| 137 | + return (tandem_adjust_ulaw(sr, se, y, i, 0x10, qtab_723_40)); | |
| 138 | + case AUDIO_ENCODING_LINEAR: | |
| 139 | + return (sr << 2); /* sr was of 14-bit dynamic range */ | |
| 140 | + default: | |
| 141 | + return (-1); | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + | |
| 146 | + /** Encodes the input chunk in_buff of linear PCM, A-law or u-law data and returns | |
| 147 | + * the G726_40 encoded chuck into out_buff. <br> | |
| 148 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 149 | + * in_coding value. */ | |
| 150 | + public static int encode(byte[] in_buff, int in_offset, int in_len, int in_coding, byte[] out_buff, int out_offset, G726State state) { | |
| 151 | + | |
| 152 | + if (in_coding==AUDIO_ENCODING_ALAW || in_coding==AUDIO_ENCODING_ULAW) { | |
| 153 | + | |
| 154 | + int len_div_8=in_len/8; | |
| 155 | + for (int i=0; i<len_div_8; i++) { | |
| 156 | + long value8=0; | |
| 157 | + int in_index=in_offset+i*8; | |
| 158 | + for (int j=0; j<8; j++) { | |
| 159 | + int in_value=unsignedInt(in_buff[in_index+j]); | |
| 160 | + int out_value=encode(in_value,in_coding,state); | |
| 161 | + value8+=((long)out_value)<<(5*(7-j)); | |
| 162 | + } | |
| 163 | + int out_index=out_offset+i*5; | |
| 164 | + for (int k=0; k<5; k++) { | |
| 165 | + out_buff[out_index+k]=(byte)(value8>>(8*(4-k))); | |
| 166 | + } | |
| 167 | + } | |
| 168 | + return len_div_8*5; | |
| 169 | + } | |
| 170 | + else | |
| 171 | + if (in_coding==AUDIO_ENCODING_LINEAR) { | |
| 172 | + | |
| 173 | + int len_div_16=in_len/16; | |
| 174 | + for (int i=0; i<len_div_16; i++) { | |
| 175 | + long value16=0; | |
| 176 | + int in_index=in_offset+i*16; | |
| 177 | + for (int j=0; j<8; j++) { | |
| 178 | + int j2=j*2; | |
| 179 | + int in_value=signedIntLittleEndian(in_buff[in_index+j2+1],in_buff[in_index+j2]); | |
| 180 | + int out_value=encode(in_value,in_coding,state); | |
| 181 | + value16+=((long)out_value)<<(5*(7-j)); | |
| 182 | + } | |
| 183 | + int out_index=out_offset+i*5; | |
| 184 | + for (int k=0; k<5; k++) { | |
| 185 | + out_buff[out_index+k]=(byte)(value16>>(8*(4-k))); | |
| 186 | + } | |
| 187 | + } | |
| 188 | + return len_div_16*5; | |
| 189 | + } | |
| 190 | + else return -1; | |
| 191 | + } | |
| 192 | + | |
| 193 | + | |
| 194 | + /** Decodes the input chunk in_buff of G726_40 encoded data and returns | |
| 195 | + * the linear PCM, A-law or u-law chunk into out_buff. <br> | |
| 196 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 197 | + * out_coding value. */ | |
| 198 | + public static int decode(byte[] in_buff, int in_offset, int in_len, int out_coding, byte[] out_buff, int out_offset, G726State state) { | |
| 199 | + | |
| 200 | + if (out_coding==AUDIO_ENCODING_ALAW || out_coding==AUDIO_ENCODING_ULAW) { | |
| 201 | + | |
| 202 | + int len_div_5=in_len/5; | |
| 203 | + for (int i=0; i<len_div_5; i++) { | |
| 204 | + long value8=0; | |
| 205 | + int in_index=in_offset+i*5; | |
| 206 | + for (int j=0; j<5; j++) { | |
| 207 | + value8+=(long)unsignedInt(in_buff[in_index+j])<<(8*(4-j)); | |
| 208 | + } | |
| 209 | + int out_index=out_offset+i*8; | |
| 210 | + for (int k=0; k<8; k++) { | |
| 211 | + int in_value=(int)((value8>>(5*(7-k)))&0x1F); | |
| 212 | + int out_value=decode(in_value,out_coding,state); | |
| 213 | + out_buff[out_index+k]=(byte)out_value; | |
| 214 | + } | |
| 215 | + } | |
| 216 | + return len_div_5*8; | |
| 217 | + } | |
| 218 | + else | |
| 219 | + if (out_coding==AUDIO_ENCODING_LINEAR) { | |
| 220 | + | |
| 221 | + int len_div_5=in_len/5; | |
| 222 | + for (int i=0; i<len_div_5; i++) { | |
| 223 | + long value16=0; | |
| 224 | + int in_index=in_offset+i*5; | |
| 225 | + for (int j=0; j<5; j++) { | |
| 226 | + value16+=(long)unsignedInt(in_buff[in_index+j])<<(8*(4-j)); | |
| 227 | + } | |
| 228 | + int out_index=out_offset+i*16; | |
| 229 | + for (int k=0; k<8; k++) { | |
| 230 | + int k2=k*2; | |
| 231 | + int in_value=(int)((value16>>(5*(7-k)))&0x1F); | |
| 232 | + int out_value=decode(in_value,out_coding,state); | |
| 233 | + out_buff[out_index+k2]=(byte)(out_value&0xFF); | |
| 234 | + out_buff[out_index+k2+1]=(byte)(out_value>>8); | |
| 235 | + } | |
| 236 | + } | |
| 237 | + return len_div_5*16; | |
| 238 | + } | |
| 239 | + else return -1; | |
| 240 | + } | |
| 241 | + | |
| 242 | + | |
| 243 | + // ************************* NON-STATIC ************************* | |
| 244 | + | |
| 245 | + /** Creates a new G726_40 processor, that can be used to encode from or decode do PCM audio data. */ | |
| 246 | + public G726_40() { | |
| 247 | + super(40000); | |
| 248 | + } | |
| 249 | + | |
| 250 | + | |
| 251 | + /** Encodes a 16-bit linear PCM, A-law or u-law input sample and retuens | |
| 252 | + * the resulting 5-bit CCITT G.726 40kbps code. | |
| 253 | + * Returns -1 if the input coding value is invalid. */ | |
| 254 | + public int encode(int sl, int in_coding) { | |
| 255 | + return encode(sl,in_coding,state); | |
| 256 | + } | |
| 257 | + | |
| 258 | + | |
| 259 | + /** Encodes the input chunk in_buff of linear PCM, A-law or u-law data and returns | |
| 260 | + * the G726_40 encoded chuck into out_buff. <br> | |
| 261 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 262 | + * in_coding value. */ | |
| 263 | + public int encode(byte[] in_buff, int in_offset, int in_len, int in_coding, byte[] out_buff, int out_offset) { | |
| 264 | + return encode(in_buff,in_offset,in_len,in_coding,out_buff,out_offset,state); | |
| 265 | + } | |
| 266 | + | |
| 267 | + | |
| 268 | + /** Decodes a 5-bit CCITT G.726 40kbps code and returns | |
| 269 | + * the resulting 16-bit linear PCM, A-law or u-law sample value. | |
| 270 | + * -1 is returned if the output coding is unknown. */ | |
| 271 | + public int decode(int i, int out_coding) { | |
| 272 | + return decode(i,out_coding,state); | |
| 273 | + } | |
| 274 | + | |
| 275 | + | |
| 276 | + /** Decodes the input chunk in_buff of G726_40 encoded data and returns | |
| 277 | + * the linear PCM, A-law or u-law chunk into out_buff. <br> | |
| 278 | + * It returns the actual size of the output data, or -1 in case of unknown | |
| 279 | + * out_coding value. */ | |
| 280 | + public int decode(byte[] in_buff, int in_offset, int in_len, int out_coding, byte[] out_buff, int out_offset) { | |
| 281 | + return decode(in_buff,in_offset,in_len,out_coding,out_buff,out_offset,state); | |
| 282 | + } | |
| 283 | + | |
| 284 | + | |
| 285 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/entity/Audio.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/entity/Audio.java | |
| 1 | +package cn.org.hentai.jtt1078.entity; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Created by houcheng on 2019-12-11. | |
| 5 | + */ | |
| 6 | +public class Audio extends Media | |
| 7 | +{ | |
| 8 | + public Audio(long sequence, MediaEncoding.Encoding encoding, byte[] data) | |
| 9 | + { | |
| 10 | + super(sequence, encoding, data); | |
| 11 | + } | |
| 12 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/entity/Media.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/entity/Media.java | |
| 1 | +package cn.org.hentai.jtt1078.entity; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Created by houcheng on 2019-12-11. | |
| 5 | + * 数据流,可能是视频或是音频,视频为FLV封装,音频为PCM编码的片断 | |
| 6 | + */ | |
| 7 | +public class Media | |
| 8 | +{ | |
| 9 | + public enum Type { Video, Audio }; | |
| 10 | + | |
| 11 | + public Type type; | |
| 12 | + public MediaEncoding.Encoding encoding; | |
| 13 | + public long sequence; | |
| 14 | + public byte[] data; | |
| 15 | + | |
| 16 | + public Media(long seq, MediaEncoding.Encoding encoding, byte[] data) | |
| 17 | + { | |
| 18 | + this.type = type; | |
| 19 | + this.data = data; | |
| 20 | + this.encoding = encoding; | |
| 21 | + this.sequence = seq; | |
| 22 | + } | |
| 23 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/entity/MediaEncoding.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/entity/MediaEncoding.java | |
| 1 | +package cn.org.hentai.jtt1078.entity; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Created by matrixy on 2019/12/20. | |
| 5 | + */ | |
| 6 | +public final class MediaEncoding | |
| 7 | +{ | |
| 8 | + public enum Encoding | |
| 9 | + { | |
| 10 | + RESERVED, | |
| 11 | + G721, | |
| 12 | + G722, | |
| 13 | + G723, | |
| 14 | + G728, | |
| 15 | + G729, | |
| 16 | + G711A, | |
| 17 | + G711U, | |
| 18 | + G726, | |
| 19 | + G729A, | |
| 20 | + DVI4_3, | |
| 21 | + DVI4_4, | |
| 22 | + DVI4_8K, | |
| 23 | + DVI4_16K, | |
| 24 | + LPC, | |
| 25 | + S16BE_STEREO, | |
| 26 | + S16BE_MONO, | |
| 27 | + MPEGAUDIO, | |
| 28 | + LPCM, | |
| 29 | + AAC, | |
| 30 | + WMA9STD, | |
| 31 | + HEAAC, | |
| 32 | + PCM_VOICE, | |
| 33 | + PCM_AUDIO, | |
| 34 | + AACLC, | |
| 35 | + MP3, | |
| 36 | + ADPCMA, | |
| 37 | + MP4AUDIO, | |
| 38 | + AMR, // 28 | |
| 39 | + | |
| 40 | + H264, // 98 | |
| 41 | + H265, | |
| 42 | + AVS, | |
| 43 | + SVAC, | |
| 44 | + UNKNOWN | |
| 45 | + } | |
| 46 | + | |
| 47 | + public static Encoding getEncoding(Media.Type type, int pt) | |
| 48 | + { | |
| 49 | + if (type.equals(Media.Type.Audio)) | |
| 50 | + { | |
| 51 | + if (pt >= 0 && pt <= 28) return Encoding.values()[pt]; | |
| 52 | + else return Encoding.UNKNOWN; | |
| 53 | + } | |
| 54 | + else | |
| 55 | + { | |
| 56 | + if (pt >= 98 && pt <= 101) return Encoding.values()[pt - 98 + 29]; | |
| 57 | + else return Encoding.UNKNOWN; | |
| 58 | + } | |
| 59 | + } | |
| 60 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/entity/Video.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/entity/Video.java | |
| 1 | +package cn.org.hentai.jtt1078.entity; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Created by houcheng on 2019-12-11. | |
| 5 | + */ | |
| 6 | +public class Video extends Media | |
| 7 | +{ | |
| 8 | + public Video(long sequence, MediaEncoding.Encoding encoding, byte[] data) | |
| 9 | + { | |
| 10 | + super(sequence, encoding, data); | |
| 11 | + } | |
| 12 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/flv/AudioTag.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/flv/AudioTag.java | |
| 1 | +package cn.org.hentai.jtt1078.flv; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * flv 音频封装 | |
| 5 | + * author:zhouyili (11861744@qq.com) | |
| 6 | + */ | |
| 7 | +public class AudioTag extends FlvTag{ | |
| 8 | + public static final byte MP3 = 2; | |
| 9 | + //UB[4] | |
| 10 | + //0 = Linear PCM, platform endian 根据编码的平台,一般用3而不用这个 | |
| 11 | + //1 = ADPCM | |
| 12 | + //2 = MP3 | |
| 13 | + //3 = Linear PCM, little endian | |
| 14 | + //4 = Nellymoser 16-kHz mono | |
| 15 | + //5 = Nellymoser 8-kHz mono | |
| 16 | + //6 = Nellymoser | |
| 17 | + //7 = G.711 A-law logarithmic PCM 8 = G.711 mu-law logarithmic PCM 9 = reserved | |
| 18 | + //10 = AAC | |
| 19 | + //11 = Speex | |
| 20 | + //14 = MP3 8-Khz | |
| 21 | + //15 = Device-specific sound | |
| 22 | + private byte format; | |
| 23 | + //0:5.5kHz,1:11kHz,2:22kHz,3:44kHz,format==10的话为3 | |
| 24 | + private byte rate; | |
| 25 | + //0:8bit,1:16bit | |
| 26 | + private byte size; | |
| 27 | + //0:mono,1:stereo,format==10的话为1 | |
| 28 | + private byte type; | |
| 29 | + //format==10是aac data,否则是对应编码后的数据 | |
| 30 | + private byte[] data; | |
| 31 | + | |
| 32 | + public AudioTag() { | |
| 33 | + } | |
| 34 | + | |
| 35 | + public AudioTag(int offSetTimestamp, int tagDataSize, byte format, byte rate, byte size, byte type, byte[] data) { | |
| 36 | + super(offSetTimestamp, tagDataSize); | |
| 37 | + this.format = format; | |
| 38 | + this.rate = rate; | |
| 39 | + this.size = size; | |
| 40 | + this.type = type; | |
| 41 | + this.data = data; | |
| 42 | + } | |
| 43 | + | |
| 44 | + public byte getFormat() { | |
| 45 | + return format; | |
| 46 | + } | |
| 47 | + | |
| 48 | + public void setFormat(byte format) { | |
| 49 | + this.format = format; | |
| 50 | + } | |
| 51 | + | |
| 52 | + public byte getRate() { | |
| 53 | + return rate; | |
| 54 | + } | |
| 55 | + | |
| 56 | + public void setRate(byte rate) { | |
| 57 | + this.rate = rate; | |
| 58 | + } | |
| 59 | + | |
| 60 | + public byte getSize() { | |
| 61 | + return size; | |
| 62 | + } | |
| 63 | + | |
| 64 | + public void setSize(byte size) { | |
| 65 | + this.size = size; | |
| 66 | + } | |
| 67 | + | |
| 68 | + public byte getType() { | |
| 69 | + return type; | |
| 70 | + } | |
| 71 | + | |
| 72 | + public void setType(byte type) { | |
| 73 | + this.type = type; | |
| 74 | + } | |
| 75 | + | |
| 76 | + public byte[] getData() { | |
| 77 | + return data; | |
| 78 | + } | |
| 79 | + | |
| 80 | + public void setData(byte[] data) { | |
| 81 | + this.data = data; | |
| 82 | + } | |
| 83 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/flv/FlvAudioTagEncoder.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/flv/FlvAudioTagEncoder.java | |
| 1 | +package cn.org.hentai.jtt1078.flv; | |
| 2 | + | |
| 3 | +import io.netty.buffer.ByteBuf; | |
| 4 | +import io.netty.buffer.Unpooled; | |
| 5 | +import org.slf4j.Logger; | |
| 6 | +import org.slf4j.LoggerFactory; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * Flv 音频封装编码 | |
| 10 | + * author:zhouyili (11861744@qq.com) | |
| 11 | + */ | |
| 12 | +public class FlvAudioTagEncoder { | |
| 13 | + public static final Logger log = LoggerFactory.getLogger(FlvAudioTagEncoder.class); | |
| 14 | + | |
| 15 | + public ByteBuf encode(AudioTag audioTag) throws Exception { | |
| 16 | + ByteBuf buffer = Unpooled.buffer(); | |
| 17 | + if (audioTag == null) return buffer; | |
| 18 | +// buffer.writeInt(audioTag.getPreTagSize()); | |
| 19 | + //----------------------tag header begin------- | |
| 20 | + buffer.writeByte(8); | |
| 21 | + buffer.writeMedium(audioTag.getTagDataSize()); | |
| 22 | + buffer.writeMedium(audioTag.getOffSetTimestamp() & 0xFFFFFF); | |
| 23 | + buffer.writeByte(audioTag.getOffSetTimestamp() >> 24); | |
| 24 | + buffer.writeMedium(audioTag.getStreamId()); | |
| 25 | + //---------------------tag header length 11--------- | |
| 26 | + //---------------------tag header end---------------- | |
| 27 | + byte formatAndRateAndSize = (byte) (audioTag.getFormat() << 4 | audioTag.getRate() << 2 | audioTag.getSize() << 1 | audioTag.getType()); | |
| 28 | + //-------------data begin------- | |
| 29 | + buffer.writeByte(formatAndRateAndSize); | |
| 30 | + buffer.writeBytes(audioTag.getData()); | |
| 31 | + //-------------data end ------- | |
| 32 | + buffer.writeInt(buffer.writerIndex());//应该等于11+tagDataSize | |
| 33 | + return buffer; | |
| 34 | + } | |
| 35 | + | |
| 36 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/flv/FlvEncoder.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/flv/FlvEncoder.java | |
| 1 | +package cn.org.hentai.jtt1078.flv; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.util.Packet; | |
| 4 | + | |
| 5 | +import java.io.ByteArrayOutputStream; | |
| 6 | +import java.io.IOException; | |
| 7 | +import java.io.OutputStream; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * Created by matrixy on 2020/1/3. | |
| 11 | + */ | |
| 12 | +public final class FlvEncoder | |
| 13 | +{ | |
| 14 | + Packet flvHeader; | |
| 15 | + Packet videoHeader; | |
| 16 | + Packet SPS, PPS; | |
| 17 | + int SPSSize, PPSSize; | |
| 18 | + boolean writeAVCSeqHeader; | |
| 19 | + int prevTagSize; | |
| 20 | + int streamID; | |
| 21 | + int videoTimeStamp; | |
| 22 | + byte[] lastIFrame; | |
| 23 | + | |
| 24 | + Packet _pAudioSpecificConfig; | |
| 25 | + int _nAudioConfigSize; | |
| 26 | + int _aacProfile; | |
| 27 | + int _sampleRateIndex; | |
| 28 | + int _channelConfig; | |
| 29 | + int _bWriteAACSeqHeader; | |
| 30 | + | |
| 31 | + boolean haveAudio, haveVideo; | |
| 32 | + | |
| 33 | + ByteArrayOutputStream videoFrame; | |
| 34 | + | |
| 35 | + public FlvEncoder(boolean haveVideo, boolean haveAudio) | |
| 36 | + { | |
| 37 | + this.haveVideo = haveVideo; | |
| 38 | + // this.haveAudio = haveAudio; | |
| 39 | + flvHeader = Packet.create(16); | |
| 40 | + videoFrame = new ByteArrayOutputStream(2048 * 100); | |
| 41 | + makeFlvHeader(); | |
| 42 | + } | |
| 43 | + | |
| 44 | + public Packet getHeader() | |
| 45 | + { | |
| 46 | + return flvHeader; | |
| 47 | + } | |
| 48 | + | |
| 49 | + public Packet getVideoHeader() | |
| 50 | + { | |
| 51 | + return videoHeader; | |
| 52 | + } | |
| 53 | + | |
| 54 | + public boolean videoReady() | |
| 55 | + { | |
| 56 | + return writeAVCSeqHeader; | |
| 57 | + } | |
| 58 | + | |
| 59 | + public byte[] getLastIFrame() | |
| 60 | + { | |
| 61 | + return this.lastIFrame; | |
| 62 | + } | |
| 63 | + | |
| 64 | + public byte[] write(byte[] nalu, int nTimeStamp) | |
| 65 | + { | |
| 66 | + this.videoTimeStamp = nTimeStamp; | |
| 67 | + | |
| 68 | + if (nalu == null || nalu.length <= 4) return null; | |
| 69 | + | |
| 70 | + int naluType = nalu[4] & 0x1f; | |
| 71 | + // skip SEI | |
| 72 | + if (naluType == 0x06) return null; | |
| 73 | + if (naluType == 0x01 | |
| 74 | + || naluType == 0x02 | |
| 75 | + || naluType == 0x03 | |
| 76 | + || naluType == 0x04 | |
| 77 | + || naluType == 0x05 | |
| 78 | + || naluType == 0x07 | |
| 79 | + || naluType == 0x08) ; else return null; | |
| 80 | + | |
| 81 | + if (SPS == null && naluType == 0x07) | |
| 82 | + { | |
| 83 | + SPS = Packet.create(nalu); | |
| 84 | + SPSSize = nalu.length; | |
| 85 | + } | |
| 86 | + if (PPS == null && naluType == 0x08) | |
| 87 | + { | |
| 88 | + PPS = Packet.create(nalu); | |
| 89 | + PPSSize = nalu.length; | |
| 90 | + } | |
| 91 | + if (SPS != null && PPS != null && writeAVCSeqHeader == false) | |
| 92 | + { | |
| 93 | + writeH264Header(nTimeStamp); | |
| 94 | + writeAVCSeqHeader = true; | |
| 95 | + } | |
| 96 | + if (writeAVCSeqHeader == false) return null; | |
| 97 | + | |
| 98 | + videoFrame.reset(); | |
| 99 | + writeH264Frame(nalu, nTimeStamp); | |
| 100 | + | |
| 101 | + if (videoFrame.size() == 0) return null; | |
| 102 | + | |
| 103 | + // 如果当前NAL单元为I祯,则缓存一个 | |
| 104 | + if (naluType == 0x05) | |
| 105 | + { | |
| 106 | + lastIFrame = videoFrame.toByteArray(); | |
| 107 | + } | |
| 108 | + | |
| 109 | + return videoFrame.toByteArray(); | |
| 110 | + } | |
| 111 | + | |
| 112 | + void makeFlvHeader() | |
| 113 | + { | |
| 114 | + flvHeader.addByte((byte)'F'); | |
| 115 | + flvHeader.addByte((byte)'L'); | |
| 116 | + flvHeader.addByte((byte)'V'); | |
| 117 | + flvHeader.addByte((byte)0x01); // version | |
| 118 | + flvHeader.addByte((byte)(0x00 | (haveVideo ? 0x01 : 0x00) | (haveAudio ? 0x04 : 0x00))); | |
| 119 | + flvHeader.addInt(0x09); | |
| 120 | + flvHeader.addInt(0x00); | |
| 121 | + } | |
| 122 | + | |
| 123 | + void writeH264Header(int nTimeStamp) | |
| 124 | + { | |
| 125 | + int nDataSize = 1 + 1 + 3 + 6 + 2 + (SPSSize - 4) + 1 + 2 + (PPSSize - 4); | |
| 126 | + videoHeader = Packet.create(nDataSize + 32); | |
| 127 | + | |
| 128 | + byte cTagType = 0x09; | |
| 129 | + videoHeader.addByte(cTagType); | |
| 130 | + | |
| 131 | + videoHeader.add3Bytes(nDataSize); | |
| 132 | + | |
| 133 | + videoHeader.add3Bytes(nTimeStamp); | |
| 134 | + videoHeader.addByte((byte)(nTimeStamp >> 24)); | |
| 135 | + | |
| 136 | + videoHeader.add3Bytes(streamID); | |
| 137 | + | |
| 138 | + byte cVideoParam = 0x17; | |
| 139 | + videoHeader.addByte(cVideoParam); | |
| 140 | + | |
| 141 | + byte cAVCPacketType = 0x00; | |
| 142 | + videoHeader.addByte(cAVCPacketType); | |
| 143 | + | |
| 144 | + videoHeader.add3Bytes(0x00); | |
| 145 | + | |
| 146 | + videoHeader.addByte((byte)0x01); | |
| 147 | + | |
| 148 | + videoHeader.addByte(SPS.seek(5).nextByte()); | |
| 149 | + videoHeader.addByte(SPS.seek(6).nextByte()); | |
| 150 | + videoHeader.addByte(SPS.seek(7).nextByte()); | |
| 151 | + videoHeader.addByte((byte)0xff); | |
| 152 | + videoHeader.addByte((byte)0xe1); | |
| 153 | + | |
| 154 | + videoHeader.addShort((short)(SPSSize - 4)); | |
| 155 | + videoHeader.addBytes(SPS.seek(4).nextBytes()); | |
| 156 | + videoHeader.addByte((byte)0x01); | |
| 157 | + | |
| 158 | + videoHeader.addShort((short)(PPSSize - 4)); | |
| 159 | + videoHeader.addBytes(PPS.seek(4).nextBytes()); | |
| 160 | + | |
| 161 | + prevTagSize = 11 + nDataSize; | |
| 162 | + videoHeader.addInt(prevTagSize); | |
| 163 | + } | |
| 164 | + | |
| 165 | + void writeH264Frame(byte[] nalu, int nTimeStamp) | |
| 166 | + { | |
| 167 | + int nNaluType = nalu[4] & 0x1f; | |
| 168 | + if (nNaluType == 7 || nNaluType == 8) return; | |
| 169 | + | |
| 170 | + writeByte(0x09); | |
| 171 | + | |
| 172 | + int nDataSize = 1 + 1 + 3 + 4 + (nalu.length - 4); | |
| 173 | + writeU3(nDataSize); | |
| 174 | + | |
| 175 | + writeU3(nTimeStamp); | |
| 176 | + writeByte(nTimeStamp >> 24); | |
| 177 | + | |
| 178 | + writeU3(streamID); | |
| 179 | + | |
| 180 | + if (nNaluType == 5) writeByte(0x17); | |
| 181 | + else writeByte(0x27); | |
| 182 | + | |
| 183 | + writeByte(0x01); | |
| 184 | + writeU3(0x00); | |
| 185 | + writeU4(nalu.length - 4); | |
| 186 | + writeBytes(nalu, 4, nalu.length - 4); | |
| 187 | + | |
| 188 | + prevTagSize = 11 + nDataSize; | |
| 189 | + | |
| 190 | + writeU4(prevTagSize); | |
| 191 | + } | |
| 192 | + | |
| 193 | + void write(byte u) | |
| 194 | + { | |
| 195 | + videoFrame.write(u); | |
| 196 | + } | |
| 197 | + | |
| 198 | + void writeBytes(byte[] data) | |
| 199 | + { | |
| 200 | + videoFrame.write(data, 0, data.length); | |
| 201 | + } | |
| 202 | + | |
| 203 | + void writeBytes(byte[] data, int offset, int len) | |
| 204 | + { | |
| 205 | + videoFrame.write(data, offset, len); | |
| 206 | + } | |
| 207 | + | |
| 208 | + void writeU4(int i) | |
| 209 | + { | |
| 210 | + write((byte)((i >> 24) & 0xff)); | |
| 211 | + write((byte)((i >> 16) & 0xff)); | |
| 212 | + write((byte)((i >> 8) & 0xff)); | |
| 213 | + write((byte)((i >> 0) & 0xff)); | |
| 214 | + } | |
| 215 | + | |
| 216 | + void writeU3(int i) | |
| 217 | + { | |
| 218 | + write((byte)((i >> 16) & 0xff)); | |
| 219 | + write((byte)((i >> 8) & 0xff)); | |
| 220 | + write((byte)((i >> 0) & 0xff)); | |
| 221 | + } | |
| 222 | + | |
| 223 | + void writeU2(int i) | |
| 224 | + { | |
| 225 | + write((byte)((i >> 8) & 0xff)); | |
| 226 | + write((byte)((i >> 0) & 0xff)); | |
| 227 | + } | |
| 228 | + | |
| 229 | + void writeByte(int i) | |
| 230 | + { | |
| 231 | + write((byte)(i & 0xff)); | |
| 232 | + } | |
| 233 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/flv/FlvTag.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/flv/FlvTag.java | |
| 1 | +package cn.org.hentai.jtt1078.flv; | |
| 2 | + | |
| 3 | +//由于flv格式是header+body,而body是preTagSize+currentTagData.......循环形式,并且第一个的preTagSize始终为0, | |
| 4 | +// 与其这样不如采用(header+preTag0Size)+(currentTagData+currentTagSize),这样就避免每次都要记录以下上一个tag的数据大小。 | |
| 5 | +/** | |
| 6 | + * author:zhouyili (11861744@qq.com) | |
| 7 | + */ | |
| 8 | +public class FlvTag { | |
| 9 | + public static final int AUDIO = 8; | |
| 10 | + public static final int VIDEO = 9; | |
| 11 | + public static final int SCRIPT = 18;//0x12 | |
| 12 | + private int preTagSize; | |
| 13 | + private byte tagType; | |
| 14 | + /**3个字节 streamId以后的数据长度*/ | |
| 15 | + private int tagDataSize; | |
| 16 | + //低3个字节写入后再写高位字节,相对于第一帧的时间偏移量,单位ms | |
| 17 | + private int offSetTimestamp; | |
| 18 | + //3个字节,一般总是0 | |
| 19 | + private int streamId; | |
| 20 | + | |
| 21 | + public FlvTag() { | |
| 22 | + } | |
| 23 | + | |
| 24 | + public FlvTag(int offSetTimestamp, int tagDataSize) { | |
| 25 | + this.tagDataSize = tagDataSize; | |
| 26 | + this.offSetTimestamp = offSetTimestamp; | |
| 27 | + } | |
| 28 | + | |
| 29 | + public int getPreTagSize() { | |
| 30 | + return preTagSize; | |
| 31 | + } | |
| 32 | + | |
| 33 | + public void setPreTagSize(int preTagSize) { | |
| 34 | + this.preTagSize = preTagSize; | |
| 35 | + } | |
| 36 | + | |
| 37 | + public byte getTagType() { | |
| 38 | + return tagType; | |
| 39 | + } | |
| 40 | + | |
| 41 | + public void setTagType(byte tagType) { | |
| 42 | + this.tagType = tagType; | |
| 43 | + } | |
| 44 | + | |
| 45 | + public int getTagDataSize() { | |
| 46 | + return tagDataSize; | |
| 47 | + } | |
| 48 | + | |
| 49 | + public void setTagDataSize(int tagDataSize) { | |
| 50 | + this.tagDataSize = tagDataSize; | |
| 51 | + } | |
| 52 | + | |
| 53 | + public int getOffSetTimestamp() { | |
| 54 | + return offSetTimestamp; | |
| 55 | + } | |
| 56 | + | |
| 57 | + public void setOffSetTimestamp(int offSetTimestamp) { | |
| 58 | + this.offSetTimestamp = offSetTimestamp; | |
| 59 | + } | |
| 60 | + | |
| 61 | + public int getStreamId() { | |
| 62 | + return streamId; | |
| 63 | + } | |
| 64 | + | |
| 65 | + public void setStreamId(int streamId) { | |
| 66 | + this.streamId = streamId; | |
| 67 | + } | |
| 68 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/http/GeneralResponseWriter.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/http/GeneralResponseWriter.java | |
| 1 | +package cn.org.hentai.jtt1078.http; | |
| 2 | + | |
| 3 | +import io.netty.buffer.ByteBuf; | |
| 4 | +import io.netty.channel.ChannelHandlerContext; | |
| 5 | +import io.netty.handler.codec.MessageToByteEncoder; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Created by matrixy on 2019/11/25. | |
| 9 | + */ | |
| 10 | +public class GeneralResponseWriter extends MessageToByteEncoder<byte[]> | |
| 11 | +{ | |
| 12 | + @Override | |
| 13 | + protected void encode(ChannelHandlerContext ctx, byte[] msg, ByteBuf out) throws Exception | |
| 14 | + { | |
| 15 | + out.writeBytes(msg); | |
| 16 | + } | |
| 17 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/http/NettyHttpServerHandler.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/http/NettyHttpServerHandler.java | |
| 1 | +package cn.org.hentai.jtt1078.http; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.entity.Media; | |
| 4 | +import cn.org.hentai.jtt1078.publisher.PublishManager; | |
| 5 | +import cn.org.hentai.jtt1078.server.Session; | |
| 6 | +import cn.org.hentai.jtt1078.util.*; | |
| 7 | +import io.netty.buffer.ByteBuf; | |
| 8 | +import io.netty.buffer.Unpooled; | |
| 9 | +import io.netty.channel.ChannelHandlerContext; | |
| 10 | +import io.netty.channel.ChannelInboundHandlerAdapter; | |
| 11 | +import io.netty.handler.codec.http.*; | |
| 12 | +import io.netty.util.Attribute; | |
| 13 | +import io.netty.util.AttributeKey; | |
| 14 | +import org.slf4j.Logger; | |
| 15 | +import org.slf4j.LoggerFactory; | |
| 16 | + | |
| 17 | +import java.io.File; | |
| 18 | +import java.io.FileInputStream; | |
| 19 | +import java.net.InetSocketAddress; | |
| 20 | +import java.util.Base64; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * Created by matrixy on 2019/8/13. | |
| 24 | + */ | |
| 25 | +public class NettyHttpServerHandler extends ChannelInboundHandlerAdapter | |
| 26 | +{ | |
| 27 | + static Logger logger = LoggerFactory.getLogger(NettyHttpServerHandler.class); | |
| 28 | + static final byte[] HTTP_403_DATA = "<h1>403 Forbidden</h1><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding--><!--padding-->".getBytes(); | |
| 29 | + static final String HEADER_ENCODING = "ISO-8859-1"; | |
| 30 | + | |
| 31 | + private static final AttributeKey<Session> SESSION_KEY = AttributeKey.valueOf("session"); | |
| 32 | + | |
| 33 | + @Override | |
| 34 | + public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception | |
| 35 | + { | |
| 36 | + FullHttpRequest fhr = (FullHttpRequest) msg; | |
| 37 | + String uri = fhr.uri(); | |
| 38 | + Packet resp = Packet.create(1024); | |
| 39 | + // uri的第二段,就是通道标签 | |
| 40 | + if (uri.startsWith("/video/")) | |
| 41 | + { | |
| 42 | + String tag = uri.substring("/video/".length()); | |
| 43 | + | |
| 44 | + resp.addBytes("HTTP/1.1 200 OK\r\n".getBytes(HEADER_ENCODING)); | |
| 45 | + resp.addBytes("Connection: keep-alive\r\n".getBytes(HEADER_ENCODING)); | |
| 46 | + resp.addBytes("Content-Type: video/x-flv\r\n".getBytes(HEADER_ENCODING)); | |
| 47 | + resp.addBytes("Transfer-Encoding: chunked\r\n".getBytes(HEADER_ENCODING)); | |
| 48 | + resp.addBytes("Cache-Control: no-cache\r\n".getBytes(HEADER_ENCODING)); | |
| 49 | + resp.addBytes("Access-Control-Allow-Origin: *\r\n".getBytes(HEADER_ENCODING)); | |
| 50 | + resp.addBytes("Access-Control-Allow-Credentials: true\r\n".getBytes(HEADER_ENCODING)); | |
| 51 | + resp.addBytes("\r\n".getBytes(HEADER_ENCODING)); | |
| 52 | + | |
| 53 | + ctx.writeAndFlush(resp.getBytes()).await(); | |
| 54 | + | |
| 55 | + // 订阅视频数据 | |
| 56 | + long wid = PublishManager.getInstance().subscribe(tag, Media.Type.Video, ctx).getId(); | |
| 57 | + setSession(ctx, new Session().set("subscriber-id", wid).set("tag", tag)); | |
| 58 | + } | |
| 59 | + else if (uri.equals("/test/multimedia")) | |
| 60 | + { | |
| 61 | + responseHTMLFile("/multimedia.html", ctx); | |
| 62 | + } | |
| 63 | + else | |
| 64 | + { | |
| 65 | + | |
| 66 | + ByteBuf body = Unpooled.buffer(HTTP_403_DATA.length); | |
| 67 | + body.writeBytes(HTTP_403_DATA); | |
| 68 | + FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(403), body); | |
| 69 | + response.headers().add("Content-Length", HTTP_403_DATA.length); | |
| 70 | + ctx.writeAndFlush(response).await(); | |
| 71 | + ctx.flush(); | |
| 72 | + } | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Override | |
| 76 | + public void channelInactive(ChannelHandlerContext ctx) throws Exception | |
| 77 | + { | |
| 78 | + super.channelInactive(ctx); | |
| 79 | + Session session = getSession(ctx); | |
| 80 | + if (session != null && session.has("subscriber-id") && session.has("tag")) | |
| 81 | + { | |
| 82 | + String tag = session.get("tag"); | |
| 83 | + Long wid = session.get("subscriber-id"); | |
| 84 | + PublishManager.getInstance().unsubscribe(tag, wid); | |
| 85 | + } | |
| 86 | + } | |
| 87 | + | |
| 88 | + // 响应静态文件内容 | |
| 89 | + private void responseHTMLFile(String htmlFilePath, ChannelHandlerContext ctx) | |
| 90 | + { | |
| 91 | + byte[] fileData = FileUtils.read(NettyHttpServerHandler.class.getResourceAsStream(htmlFilePath)); | |
| 92 | + ByteBuf body = Unpooled.buffer(fileData.length); | |
| 93 | + body.writeBytes(fileData); | |
| 94 | + FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(200), body); | |
| 95 | + response.headers().add("Content-Length", fileData.length); | |
| 96 | + ctx.write(response); | |
| 97 | + ctx.flush(); | |
| 98 | + } | |
| 99 | + | |
| 100 | + @Override | |
| 101 | + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception | |
| 102 | + { | |
| 103 | + ctx.flush(); | |
| 104 | + } | |
| 105 | + | |
| 106 | + @Override | |
| 107 | + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception | |
| 108 | + { | |
| 109 | + ctx.close(); | |
| 110 | + cause.printStackTrace(); | |
| 111 | + } | |
| 112 | + | |
| 113 | + public final void setSession(ChannelHandlerContext context, Session session) | |
| 114 | + { | |
| 115 | + context.channel().attr(SESSION_KEY).set(session); | |
| 116 | + } | |
| 117 | + | |
| 118 | + public final Session getSession(ChannelHandlerContext context) | |
| 119 | + { | |
| 120 | + Attribute<Session> attr = context.channel().attr(SESSION_KEY); | |
| 121 | + if (null == attr) return null; | |
| 122 | + else return attr.get(); | |
| 123 | + } | |
| 124 | +} | |
| 125 | + | ... | ... |
src/main/java/cn/org/hentai/jtt1078/publisher/Channel.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/publisher/Channel.java | |
| 1 | +package cn.org.hentai.jtt1078.publisher; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.codec.AudioCodec; | |
| 4 | +import cn.org.hentai.jtt1078.entity.Media; | |
| 5 | +import cn.org.hentai.jtt1078.entity.MediaEncoding; | |
| 6 | +import cn.org.hentai.jtt1078.flv.FlvEncoder; | |
| 7 | +import cn.org.hentai.jtt1078.subscriber.RTMPPublisher; | |
| 8 | +import cn.org.hentai.jtt1078.subscriber.Subscriber; | |
| 9 | +import cn.org.hentai.jtt1078.subscriber.VideoSubscriber; | |
| 10 | +import cn.org.hentai.jtt1078.util.ByteHolder; | |
| 11 | +import cn.org.hentai.jtt1078.util.Configs; | |
| 12 | +import io.netty.channel.ChannelHandlerContext; | |
| 13 | +import org.apache.commons.lang.StringUtils; | |
| 14 | +import org.slf4j.Logger; | |
| 15 | +import org.slf4j.LoggerFactory; | |
| 16 | + | |
| 17 | +import java.util.Iterator; | |
| 18 | +import java.util.concurrent.ConcurrentLinkedQueue; | |
| 19 | + | |
| 20 | +/** | |
| 21 | + * Created by matrixy on 2020/1/11. | |
| 22 | + */ | |
| 23 | +public class Channel | |
| 24 | +{ | |
| 25 | + static Logger logger = LoggerFactory.getLogger(Channel.class); | |
| 26 | + | |
| 27 | + ConcurrentLinkedQueue<Subscriber> subscribers; | |
| 28 | + RTMPPublisher rtmpPublisher; | |
| 29 | + | |
| 30 | + String tag; | |
| 31 | + boolean publishing; | |
| 32 | + ByteHolder buffer; | |
| 33 | + AudioCodec audioCodec; | |
| 34 | + FlvEncoder flvEncoder; | |
| 35 | + private long firstTimestamp = -1; | |
| 36 | + | |
| 37 | + public Channel(String tag) | |
| 38 | + { | |
| 39 | + this.tag = tag; | |
| 40 | + this.subscribers = new ConcurrentLinkedQueue<Subscriber>(); | |
| 41 | + this.flvEncoder = new FlvEncoder(true, true); | |
| 42 | + this.buffer = new ByteHolder(2048 * 100); | |
| 43 | + | |
| 44 | + if (StringUtils.isEmpty(Configs.get("rtmp.url")) == false) | |
| 45 | + { | |
| 46 | + rtmpPublisher = new RTMPPublisher(tag); | |
| 47 | + rtmpPublisher.start(); | |
| 48 | + } | |
| 49 | + } | |
| 50 | + | |
| 51 | + public boolean isPublishing() | |
| 52 | + { | |
| 53 | + return publishing; | |
| 54 | + } | |
| 55 | + | |
| 56 | + public Subscriber subscribe(ChannelHandlerContext ctx) | |
| 57 | + { | |
| 58 | + logger.info("channel: {} -> {}, subscriber: {}", Long.toHexString(hashCode() & 0xffffffffL), tag, ctx.channel().remoteAddress().toString()); | |
| 59 | + | |
| 60 | + Subscriber subscriber = new VideoSubscriber(this.tag, ctx); | |
| 61 | + this.subscribers.add(subscriber); | |
| 62 | + return subscriber; | |
| 63 | + } | |
| 64 | + | |
| 65 | + public void writeAudio(long timestamp, int pt, byte[] data) | |
| 66 | + { | |
| 67 | + if (audioCodec == null) | |
| 68 | + { | |
| 69 | + audioCodec = AudioCodec.getCodec(pt); | |
| 70 | + logger.info("audio codec: {}", MediaEncoding.getEncoding(Media.Type.Audio, pt)); | |
| 71 | + } | |
| 72 | + broadcastAudio(timestamp, audioCodec.toPCM(data)); | |
| 73 | + } | |
| 74 | + | |
| 75 | + public void writeVideo(long sequence, long timeoffset, int payloadType, byte[] h264) | |
| 76 | + { | |
| 77 | + if (firstTimestamp == -1) firstTimestamp = timeoffset; | |
| 78 | + this.publishing = true; | |
| 79 | + this.buffer.write(h264); | |
| 80 | + while (true) | |
| 81 | + { | |
| 82 | + byte[] nalu = readNalu(); | |
| 83 | + if (nalu == null) break; | |
| 84 | + if (nalu.length < 4) continue; | |
| 85 | + | |
| 86 | + byte[] flvTag = this.flvEncoder.write(nalu, (int) (timeoffset - firstTimestamp)); | |
| 87 | + | |
| 88 | + if (flvTag == null) continue; | |
| 89 | + | |
| 90 | + // 广播给所有的观众 | |
| 91 | + broadcastVideo(timeoffset, flvTag); | |
| 92 | + } | |
| 93 | + } | |
| 94 | + | |
| 95 | + public void writeVideoRtp(byte[] h264) { | |
| 96 | + | |
| 97 | + } | |
| 98 | + | |
| 99 | + public void broadcastVideo(long timeoffset, byte[] flvTag) | |
| 100 | + { | |
| 101 | + for (Subscriber subscriber : subscribers) | |
| 102 | + { | |
| 103 | + subscriber.onVideoData(timeoffset, flvTag, flvEncoder); | |
| 104 | + } | |
| 105 | + } | |
| 106 | + | |
| 107 | + public void broadcastAudio(long timeoffset, byte[] flvTag) | |
| 108 | + { | |
| 109 | + for (Subscriber subscriber : subscribers) | |
| 110 | + { | |
| 111 | + subscriber.onAudioData(timeoffset, flvTag, flvEncoder); | |
| 112 | + } | |
| 113 | + } | |
| 114 | + | |
| 115 | + public void unsubscribe(long watcherId) | |
| 116 | + { | |
| 117 | + for (Iterator<Subscriber> itr = subscribers.iterator(); itr.hasNext(); ) | |
| 118 | + { | |
| 119 | + Subscriber subscriber = itr.next(); | |
| 120 | + if (subscriber.getId() == watcherId) | |
| 121 | + { | |
| 122 | + itr.remove(); | |
| 123 | + subscriber.close(); | |
| 124 | + return; | |
| 125 | + } | |
| 126 | + } | |
| 127 | + } | |
| 128 | + | |
| 129 | + public void close() | |
| 130 | + { | |
| 131 | + for (Iterator<Subscriber> itr = subscribers.iterator(); itr.hasNext(); ) | |
| 132 | + { | |
| 133 | + Subscriber subscriber = itr.next(); | |
| 134 | + subscriber.close(); | |
| 135 | + itr.remove(); | |
| 136 | + } | |
| 137 | + if (rtmpPublisher != null) rtmpPublisher.close(); | |
| 138 | + } | |
| 139 | + | |
| 140 | + private byte[] readNalu() | |
| 141 | + { | |
| 142 | + for (int i = 0; i < buffer.size() - 3; i++) | |
| 143 | + { | |
| 144 | + int a = buffer.get(i + 0) & 0xff; | |
| 145 | + int b = buffer.get(i + 1) & 0xff; | |
| 146 | + int c = buffer.get(i + 2) & 0xff; | |
| 147 | + int d = buffer.get(i + 3) & 0xff; | |
| 148 | + if (a == 0x00 && b == 0x00 && c == 0x00 && d == 0x01) | |
| 149 | + { | |
| 150 | + if (i == 0) continue; | |
| 151 | + byte[] nalu = new byte[i]; | |
| 152 | + buffer.sliceInto(nalu, i); | |
| 153 | + //System.out.println(nalu.length); | |
| 154 | + //System.out.println(toHex(nalu)); | |
| 155 | + return nalu; | |
| 156 | + } | |
| 157 | + } | |
| 158 | + return null; | |
| 159 | + } | |
| 160 | + | |
| 161 | + public String toHex(byte[] bytes) { | |
| 162 | + StringBuilder sb = new StringBuilder(); | |
| 163 | + for (byte b : bytes) { | |
| 164 | + sb.append(String.format("%02X ", b)); | |
| 165 | + } | |
| 166 | + | |
| 167 | + return sb.toString(); | |
| 168 | + } | |
| 169 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/publisher/PublishManager.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/publisher/PublishManager.java | |
| 1 | +package cn.org.hentai.jtt1078.publisher; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.entity.Media; | |
| 4 | +import cn.org.hentai.jtt1078.subscriber.Subscriber; | |
| 5 | +import io.netty.channel.ChannelHandlerContext; | |
| 6 | +import org.slf4j.Logger; | |
| 7 | +import org.slf4j.LoggerFactory; | |
| 8 | + | |
| 9 | +import java.util.concurrent.ConcurrentHashMap; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * Created by houcheng on 2019-12-11. | |
| 13 | + */ | |
| 14 | +public final class PublishManager | |
| 15 | +{ | |
| 16 | + static Logger logger = LoggerFactory.getLogger(PublishManager.class); | |
| 17 | + ConcurrentHashMap<String, Channel> channels; | |
| 18 | + | |
| 19 | + private PublishManager() | |
| 20 | + { | |
| 21 | + channels = new ConcurrentHashMap<String, Channel>(); | |
| 22 | + } | |
| 23 | + | |
| 24 | + public Subscriber subscribe(String tag, Media.Type type, ChannelHandlerContext ctx) | |
| 25 | + { | |
| 26 | + Channel chl = channels.get(tag); | |
| 27 | + if (chl == null) | |
| 28 | + { | |
| 29 | + chl = new Channel(tag); | |
| 30 | + channels.put(tag, chl); | |
| 31 | + } | |
| 32 | + Subscriber subscriber = null; | |
| 33 | + if (type.equals(Media.Type.Video)) subscriber = chl.subscribe(ctx); | |
| 34 | + else throw new RuntimeException("unknown media type: " + type); | |
| 35 | + | |
| 36 | + subscriber.setName("subscriber-" + tag + "-" + subscriber.getId()); | |
| 37 | + subscriber.start(); | |
| 38 | + | |
| 39 | + return subscriber; | |
| 40 | + } | |
| 41 | + | |
| 42 | + public void publishAudio(String tag, int sequence, long timestamp, int payloadType, byte[] data) | |
| 43 | + { | |
| 44 | + Channel chl = channels.get(tag); | |
| 45 | + if (chl != null) chl.writeAudio(timestamp, payloadType, data); | |
| 46 | + } | |
| 47 | + | |
| 48 | + public void publishVideo(String tag, int sequence, long timestamp, int payloadType, byte[] data) | |
| 49 | + { | |
| 50 | + Channel chl = channels.get(tag); | |
| 51 | + if (chl != null) chl.writeVideo(sequence, timestamp, payloadType, data); | |
| 52 | + } | |
| 53 | + | |
| 54 | + public Channel open(String tag) | |
| 55 | + { | |
| 56 | + Channel chl = channels.get(tag); | |
| 57 | + if (chl == null) | |
| 58 | + { | |
| 59 | + chl = new Channel(tag); | |
| 60 | + channels.put(tag, chl); | |
| 61 | + } | |
| 62 | + if (chl.isPublishing()) throw new RuntimeException("channel already publishing"); | |
| 63 | + return chl; | |
| 64 | + } | |
| 65 | + | |
| 66 | + public void close(String tag) | |
| 67 | + { | |
| 68 | + Channel chl = channels.remove(tag); | |
| 69 | + if (chl != null) chl.close(); | |
| 70 | + } | |
| 71 | + | |
| 72 | + public void unsubscribe(String tag, long watcherId) | |
| 73 | + { | |
| 74 | + Channel chl = channels.get(tag); | |
| 75 | + if (chl != null) chl.unsubscribe(watcherId); | |
| 76 | + logger.info("unsubscribe: {} - {}", tag, watcherId); | |
| 77 | + } | |
| 78 | + static final PublishManager instance = new PublishManager(); | |
| 79 | + public static void init() { } | |
| 80 | + | |
| 81 | + public static PublishManager getInstance() | |
| 82 | + { | |
| 83 | + return instance; | |
| 84 | + } | |
| 85 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/rtp/H264Packeter.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/rtp/H264Packeter.java | |
| 1 | +package cn.org.hentai.jtt1078.rtp; | |
| 2 | + | |
| 3 | +import java.nio.ByteBuffer; | |
| 4 | +import java.util.ArrayList; | |
| 5 | +import java.util.Arrays; | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +public class H264Packeter { | |
| 9 | + | |
| 10 | + private final int MAX_PACKAGE_SIZE = 1400; | |
| 11 | + | |
| 12 | + private byte[] buffer; | |
| 13 | + | |
| 14 | + private long firstTimestamp = 0; | |
| 15 | + | |
| 16 | + private int seq = 0; | |
| 17 | + | |
| 18 | + private int lastPosition = 0; | |
| 19 | + | |
| 20 | + public List<byte[]> packet(byte[] h264, long timestamp) { | |
| 21 | + List<byte[]> streams = new ArrayList<>(); | |
| 22 | + if (buffer == null) { | |
| 23 | + buffer = Arrays.copyOf(h264, h264.length); | |
| 24 | + } else { | |
| 25 | + byte[] nbuffer = new byte[buffer.length - lastPosition + h264.length]; | |
| 26 | + System.arraycopy(buffer, lastPosition, nbuffer, 0, buffer.length - lastPosition); | |
| 27 | + //System.out.println(toHex(nbuffer)); | |
| 28 | + System.arraycopy(h264, 0, nbuffer, buffer.length - lastPosition, h264.length); | |
| 29 | + //System.out.println(toHex(nbuffer)); | |
| 30 | + buffer = nbuffer; | |
| 31 | + } | |
| 32 | + lastPosition = 0; | |
| 33 | + //System.out.println(buffer.length); | |
| 34 | + if (firstTimestamp == 0) { | |
| 35 | + firstTimestamp = timestamp; | |
| 36 | + } | |
| 37 | + while (lastPosition < buffer.length - 4) { | |
| 38 | + byte[] nalu = readNalu(); | |
| 39 | + if (nalu == null) { | |
| 40 | + break; | |
| 41 | + } | |
| 42 | + ByteBuffer buffer = null; | |
| 43 | + byte[] header = new byte[14]; | |
| 44 | + header[0] = (byte) (header[0] | 0x80); | |
| 45 | + header[1] = (byte) (header[1] | 96); | |
| 46 | + header[11] = 15; | |
| 47 | + if (nalu.length <= MAX_PACKAGE_SIZE) { | |
| 48 | + buffer = ByteBuffer.allocate(16 + nalu.length); | |
| 49 | + header[1] = (byte) (header[1] | 0x80); | |
| 50 | + buffer.put((byte) 0x24); | |
| 51 | + buffer.put((byte) 0); | |
| 52 | + buffer.putShort((short) (12 + nalu.length)); | |
| 53 | + buffer.put(header, 0, 2); | |
| 54 | + buffer.putShort((short) ++seq); | |
| 55 | + buffer.putInt((int) (timestamp - firstTimestamp)); | |
| 56 | + buffer.put(header, 8, 4); | |
| 57 | + buffer.put(nalu); | |
| 58 | + //System.out.println("完整: " + toHex(buffer.array())); | |
| 59 | + streams.add(buffer.array()); | |
| 60 | + } else { | |
| 61 | + int tail = nalu.length % MAX_PACKAGE_SIZE, group = nalu.length / MAX_PACKAGE_SIZE + (tail > 0 ? 1 : 0); | |
| 62 | + for (int i = 0; i < group; i++) { | |
| 63 | + buffer = ByteBuffer.allocate(18 + MAX_PACKAGE_SIZE); | |
| 64 | + if (i == 0) { | |
| 65 | + buffer = ByteBuffer.allocate(17 + MAX_PACKAGE_SIZE); | |
| 66 | + header[1] = (byte) (header[1] & 0x7F); | |
| 67 | + header[12] = (byte) (header[12] | ((byte) (nalu[0] & 0x80)) << 7); | |
| 68 | + header[12] = (byte) (header[12] | ((byte) ((nalu[0] & 0x60) >> 5)) << 5); | |
| 69 | + header[12] = (byte) (header[12] | ((byte) 28)); | |
| 70 | + header[13] = (byte) (header[13] & 0xBF); | |
| 71 | + header[13] = (byte) (header[13] & 0xDF); | |
| 72 | + header[13] = (byte) (header[13] | 0x80); | |
| 73 | + header[13] = (byte) (header[13] | ((byte) (nalu[0] & 0x1F))); | |
| 74 | + buffer.put((byte) 0x24); | |
| 75 | + buffer.put((byte) 0); | |
| 76 | + buffer.putShort((short) (13 + MAX_PACKAGE_SIZE)); | |
| 77 | + buffer.put(header, 0, 2); | |
| 78 | + buffer.putShort((short) ++seq); | |
| 79 | + buffer.putInt((int) (timestamp - firstTimestamp)); | |
| 80 | + buffer.put(header, 8, 6); | |
| 81 | + buffer.put(nalu, i * MAX_PACKAGE_SIZE + 1, MAX_PACKAGE_SIZE - 1); | |
| 82 | + //System.out.println(String.format("Nalu header:%02X", nalu[0])); | |
| 83 | + //System.out.println("第一分片: " + toHex(buffer.array())); | |
| 84 | + } else if (i == group - 1) { | |
| 85 | + buffer = ByteBuffer.allocate(18 + tail); | |
| 86 | + header[1] = (byte) (header[1] | 0x80); | |
| 87 | + header[12] = (byte) (header[12] | ((byte) (nalu[0] & 0x80)) << 7); | |
| 88 | + header[12] = (byte) (header[12] | ((byte) ((nalu[0] & 0x60) >> 5)) << 5); | |
| 89 | + header[12] = (byte) (header[12] | ((byte) 28)); | |
| 90 | + header[13] = (byte) (header[13] & 0xDF); | |
| 91 | + header[13] = (byte) (header[13] & 0x7F); | |
| 92 | + header[13] = (byte) (header[13] | 0x40); | |
| 93 | + header[13] = (byte) (header[13] | ((byte) (nalu[0] & 0x1F))); | |
| 94 | + buffer.put((byte) 0x24); | |
| 95 | + buffer.put((byte) 0); | |
| 96 | + buffer.putShort((short) (14 + tail)); | |
| 97 | + buffer.put(header, 0, 2); | |
| 98 | + buffer.putShort((short) ++seq); | |
| 99 | + buffer.putInt((int) (timestamp - firstTimestamp)); | |
| 100 | + buffer.put(header, 8, 6); | |
| 101 | + buffer.put(nalu, i * MAX_PACKAGE_SIZE, tail); | |
| 102 | + //System.out.println("最后分片: " + toHex(buffer.array())); | |
| 103 | + } else { | |
| 104 | + header[1] = (byte) (header[1] & 0x7F); | |
| 105 | + header[12] = (byte) (header[12] | ((byte) (nalu[0] & 0x80)) << 7); | |
| 106 | + header[12] = (byte) (header[12] | ((byte) ((nalu[0] & 0x60) >> 5)) << 5); | |
| 107 | + header[12] = (byte) (header[12] | ((byte) 28)); | |
| 108 | + header[13] = (byte) (header[13] & 0xDF); | |
| 109 | + header[13] = (byte) (header[13] & 0x7F); | |
| 110 | + header[13] = (byte) (header[13] & 0xBF); | |
| 111 | + header[13] = (byte) (header[13] | ((byte) (nalu[0] & 0x1F))); | |
| 112 | + buffer.put((byte) 0x24); | |
| 113 | + buffer.put((byte) 0); | |
| 114 | + buffer.putShort((short) (14 + MAX_PACKAGE_SIZE)); | |
| 115 | + buffer.put(header, 0, 2); | |
| 116 | + buffer.putShort((short) ++seq); | |
| 117 | + buffer.putInt((int) (timestamp - firstTimestamp)); | |
| 118 | + buffer.put(header, 8, 6); | |
| 119 | + buffer.put(nalu, i * MAX_PACKAGE_SIZE, MAX_PACKAGE_SIZE); | |
| 120 | + //System.out.println("中间分片: " + toHex(buffer.array())); | |
| 121 | + } | |
| 122 | + streams.add(buffer.array()); | |
| 123 | + } | |
| 124 | + } | |
| 125 | + } | |
| 126 | + | |
| 127 | + return streams; | |
| 128 | + } | |
| 129 | + | |
| 130 | + public byte[] readNalu() { | |
| 131 | + for (int i = (lastPosition == 0 ? 0 : lastPosition + 1); i < buffer.length - 3; i++) { | |
| 132 | + if (buffer[i] == 0 && buffer[i + 1] == 0 && buffer[i + 2] == 0 && buffer[i + 3] == 1) { | |
| 133 | + if (i != 0) { | |
| 134 | + byte[] nalu = new byte[i - lastPosition - 4]; | |
| 135 | + System.arraycopy(buffer, lastPosition + 4, nalu, 0, i - lastPosition - 4); | |
| 136 | + lastPosition = i; | |
| 137 | + //System.out.println(toHex(nalu)); | |
| 138 | + | |
| 139 | + return nalu; | |
| 140 | + } | |
| 141 | + } | |
| 142 | + } | |
| 143 | + | |
| 144 | + return null; | |
| 145 | + } | |
| 146 | + | |
| 147 | + public String toHex(byte[] bytes) { | |
| 148 | + StringBuilder sb = new StringBuilder(); | |
| 149 | + for (byte b : bytes) { | |
| 150 | + sb.append(String.format("%02X ", b)); | |
| 151 | + } | |
| 152 | + | |
| 153 | + return sb.toString(); | |
| 154 | + } | |
| 155 | + | |
| 156 | + public static void main(String[] args) { | |
| 157 | + byte[] bytes = new byte[]{(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x67,(byte)0x4D,(byte)0x00,(byte)0x1F,(byte)0x96,(byte)0x35,(byte)0x41,(byte)0xE0,(byte)0x24,(byte)0xD3,(byte)0x70,(byte)0x50,(byte)0x10,(byte)0x50,(byte)0x20,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x68,(byte)0xEE,(byte)0x31,(byte)0xB2,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x06,(byte)0xE5,(byte)0x01,(byte)0x4A,(byte)0x80,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x65,(byte)0xB8,(byte)0x00,(byte)0x00,(byte)0x0C,(byte)0x16,(byte)0x90,(byte)0x00,(byte)0x00,(byte)0xBF,(byte)0xFE,(byte)0xD4,(byte)0xA7,(byte)0x99,(byte)0x63,(byte)0xE6,(byte)0xF9,(byte)0x5A,(byte)0x75,(byte)0xCE,(byte)0xDB,(byte)0x0C,(byte)0xD3,(byte)0xA6,(byte)0x31,(byte)0x05,(byte)0x66,(byte)0x6C,(byte)0x18,(byte)0x87,(byte)0xD0,(byte)0xF9,(byte)0xD0,(byte)0xCC,(byte)0xA3,(byte)0x57,(byte)0x07,(byte)0xDF,(byte)0x7C,(byte)0x6F,(byte)0x42,(byte)0xE9,(byte)0x8B,(byte)0x1B,(byte)0xA2,(byte)0x70,(byte)0x8C,(byte)0x80,(byte)0x00,(byte)0x00,(byte)0x1A,(byte)0xD6,(byte)0xEB,(byte)0x80,(byte)0xDE,(byte)0xE6,(byte)0xE2,(byte)0xF5,(byte)0xFF,(byte)0x33,(byte)0x98,(byte)0x97,(byte)0xCD,(byte)0xEB,(byte)0xEB,(byte)0xE5,(byte)0x60,(byte)0x00,(byte)0x00,(byte)0x0F,(byte)0xFB,(byte)0x49,(byte)0x08,(byte)0x9C,(byte)0x75,(byte)0xB4,(byte)0xDB,(byte)0xCE,(byte)0x58,(byte)0x08,(byte)0xB4,(byte)0x68,(byte)0x22,(byte)0x16,(byte)0x51,(byte)0x47,(byte)0xF3,(byte)0xD3,(byte)0x56,(byte)0xC2,(byte)0x4F,(byte)0x12,(byte)0xFD,(byte)0x2B,(byte)0xC9,(byte)0x45,(byte)0x80,(byte)0xDB,(byte)0xA4,(byte)0x62,(byte)0xEB,(byte)0xC3,(byte)0x6D,(byte)0xFE,(byte)0x36,(byte)0x20,(byte)0xAE,(byte)0xD9,(byte)0xD2,(byte)0x4C,(byte)0x9E,(byte)0x06,(byte)0xA0,(byte)0x8B,(byte)0x42,(byte)0x35,(byte)0xEC,(byte)0x64,(byte)0x03,(byte)0x22,(byte)0x29,(byte)0x26,(byte)0x19,(byte)0x70,(byte)0xCA,(byte)0x18,(byte)0xC0,(byte)0x7E,(byte)0x08,(byte)0x4F,(byte)0xEB,(byte)0xFD,(byte)0x5D,(byte)0x90,(byte)0x31,(byte)0x62,(byte)0x02,(byte)0x2E,(byte)0xBE,(byte)0x53,(byte)0xCF,(byte)0xC0,(byte)0xA8,(byte)0xAC,(byte)0xF3,(byte)0x92,(byte)0xC8,(byte)0x76,(byte)0x77,(byte)0x84,(byte)0x2F,(byte)0x76,(byte)0x45,(byte)0xF3,(byte)0xBF,(byte)0x07,(byte)0x1F,(byte)0x6D,(byte)0xC6,(byte)0x11,(byte)0xB9,(byte)0x83,(byte)0xF6,(byte)0xDF,(byte)0xA1,(byte)0x6D,(byte)0x56,(byte)0x6D,(byte)0xE0,(byte)0xFA,(byte)0xC1,(byte)0x7E,(byte)0xC5,(byte)0xC5,(byte)0x3C,(byte)0x69,(byte)0x57,(byte)0x61,(byte)0xCA,(byte)0x17,(byte)0x40,(byte)0x30,(byte)0xAE,(byte)0x4E,(byte)0x4C,(byte)0x61,(byte)0xC3,(byte)0xAF,(byte)0x6F,(byte)0xB4,(byte)0x48,(byte)0x33,(byte)0x4F,(byte)0x59,(byte)0x6D,(byte)0x88,(byte)0xA0,(byte)0x3B,(byte)0x9C,(byte)0x39,(byte)0x67,(byte)0xAD,(byte)0x0C,(byte)0xC0,(byte)0x64,(byte)0x8A,(byte)0xDB,(byte)0x95,(byte)0xB3,(byte)0xEF,(byte)0x6A,(byte)0xC0,(byte)0x9B,(byte)0xAF,(byte)0x44,(byte)0xBF,(byte)0x69,(byte)0x77,(byte)0x7D,(byte)0x2B,(byte)0xDB,(byte)0x47,(byte)0x78,(byte)0xD0,(byte)0x9C,(byte)0x79,(byte)0xA1,(byte)0xFE,(byte)0xC4,(byte)0xC4,(byte)0xAF,(byte)0x6A,(byte)0x2C,(byte)0x2D,(byte)0xF4,(byte)0xB6,(byte)0x27,(byte)0xC6,(byte)0x3C,(byte)0x71,(byte)0xC1,(byte)0x5E,(byte)0xB0,(byte)0x22,(byte)0x93,(byte)0x88,(byte)0x9C,(byte)0x98,(byte)0x3A,(byte)0x8D,(byte)0x7F,(byte)0x2E,(byte)0x48,(byte)0x53,(byte)0x2D,(byte)0xF5,(byte)0x7A,(byte)0xD0,(byte)0xC2,(byte)0x68,(byte)0xAF,(byte)0xB7,(byte)0x8C,(byte)0xF4,(byte)0xD4,(byte)0x99,(byte)0x96,(byte)0x24,(byte)0x47,(byte)0x2B,(byte)0x28,(byte)0x26,(byte)0xE4,(byte)0xBD,(byte)0xFA,(byte)0x65,(byte)0x7C,(byte)0xB3,(byte)0xA8,(byte)0x3E,(byte)0x43,(byte)0xF4,(byte)0x6D,(byte)0x50,(byte)0x7F,(byte)0xE3,(byte)0xF5,(byte)0x73,(byte)0xE6,(byte)0xF2,(byte)0x23,(byte)0x3A,(byte)0x22,(byte)0x74,(byte)0x7B,(byte)0x1E,(byte)0xDC,(byte)0xFB,(byte)0xF4,(byte)0xA8,(byte)0x97,(byte)0xB9,(byte)0x3A,(byte)0x73,(byte)0x8B,(byte)0x78,(byte)0x64,(byte)0x03,(byte)0x55,(byte)0x6E,(byte)0x52,(byte)0x7D,(byte)0x4C,(byte)0x28,(byte)0x00,(byte)0x43,(byte)0x72,(byte)0x84,(byte)0xF1,(byte)0x81,(byte)0x55,(byte)0x7B,(byte)0x8D,(byte)0x0F,(byte)0x7F,(byte)0xB4,(byte)0xEB,(byte)0xAB,(byte)0x69,(byte)0x65,(byte)0x7B,(byte)0x92,(byte)0xAC,(byte)0xB6,(byte)0xB4,(byte)0x33,(byte)0x5D,(byte)0x33,(byte)0x5D,(byte)0xC2,(byte)0xF8,(byte)0x25,(byte)0x7E,(byte)0x1D,(byte)0x1D,(byte)0xDB,(byte)0x1C,(byte)0xF8,(byte)0xBE,(byte)0x4B,(byte)0x25,(byte)0xA9,(byte)0xB5,(byte)0x8A,(byte)0x8D,(byte)0x67,(byte)0x61,(byte)0xFF,(byte)0xE3,(byte)0x18,(byte)0x1C,(byte)0x8F,(byte)0x7F,(byte)0xBA,(byte)0x50,(byte)0x47,(byte)0x10,(byte)0x5D,(byte)0xD5,(byte)0x97,(byte)0x62,(byte)0x06,(byte)0x09,(byte)0x52,(byte)0xC3,(byte)0x81,(byte)0x1A,(byte)0x58,(byte)0x87,(byte)0xFC,(byte)0x30,(byte)0x61,(byte)0x89,(byte)0xF5,(byte)0x2C,(byte)0x58,(byte)0x04,(byte)0x32,(byte)0x8B,(byte)0x3E,(byte)0x79,(byte)0xA3,(byte)0x10,(byte)0xFD,(byte)0x11,(byte)0x59,(byte)0xCA,(byte)0x08,(byte)0x48,(byte)0x24,(byte)0xDF,(byte)0x5F,(byte)0x02,(byte)0x12,(byte)0x2F,(byte)0x4C,(byte)0xDC,(byte)0xE9,(byte)0xFE,(byte)0xF0,(byte)0x21,(byte)0x5B,(byte)0xD3,(byte)0x0C,(byte)0xA7,(byte)0xF6,(byte)0xEF,(byte)0xFD,(byte)0xA4,(byte)0xB0,(byte)0xEF,(byte)0x47,(byte)0xC6,(byte)0x8F,(byte)0xB7,(byte)0x90,(byte)0xE3,(byte)0x03,(byte)0xBE,(byte)0x85,(byte)0x51,(byte)0x56,(byte)0x65,(byte)0xD3,(byte)0x6B,(byte)0xC4,(byte)0x8F,(byte)0x00,(byte)0x09,(byte)0xCC,(byte)0x0C,(byte)0x7C,(byte)0x69,(byte)0x42,(byte)0x68,(byte)0x05,(byte)0x97,(byte)0x5D,(byte)0xD8,(byte)0x66,(byte)0x8E,(byte)0x1D,(byte)0x2E,(byte)0x65,(byte)0x0B,(byte)0xCC,(byte)0x24,(byte)0x15,(byte)0xE4,(byte)0x10,(byte)0x23,(byte)0x4D,(byte)0xAE,(byte)0x01,(byte)0xCB,(byte)0xEB,(byte)0x16,(byte)0xAE,(byte)0x5A,(byte)0xA9,(byte)0xA0,(byte)0xFD,(byte)0xE8,(byte)0x62,(byte)0x57,(byte)0x8E,(byte)0x8F,(byte)0x57,(byte)0xA7,(byte)0xCC,(byte)0x6B,(byte)0xEB,(byte)0xDF,(byte)0xC1,(byte)0xBD,(byte)0xA6,(byte)0x40,(byte)0x40,(byte)0x07,(byte)0xAC,(byte)0x0A,(byte)0x40,(byte)0xD1,(byte)0xA7,(byte)0x9F,(byte)0x8D,(byte)0xE8,(byte)0x36,(byte)0xD8,(byte)0x53,(byte)0x54,(byte)0x66,(byte)0x14,(byte)0x5B,(byte)0x38,(byte)0x23,(byte)0xC5,(byte)0x72,(byte)0xA1,(byte)0x9D,(byte)0x3B,(byte)0xDD,(byte)0xD3,(byte)0xD6,(byte)0x46,(byte)0xE9,(byte)0x7D,(byte)0x0D,(byte)0xA7,(byte)0x22,(byte)0x00,(byte)0x87,(byte)0x7C,(byte)0x4E,(byte)0x4E,(byte)0x56,(byte)0xE1,(byte)0x03,(byte)0x99,(byte)0x4A,(byte)0xB5,(byte)0x09,(byte)0xD7,(byte)0xC1,(byte)0x0F,(byte)0xDD,(byte)0xB5,(byte)0x91,(byte)0xF8,(byte)0x3D,(byte)0x19,(byte)0x63,(byte)0xAD,(byte)0xC1,(byte)0x21,(byte)0x46,(byte)0x2F,(byte)0x2A,(byte)0xE8,(byte)0x11,(byte)0xFA,(byte)0x56,(byte)0xCD,(byte)0x16,(byte)0xB2,(byte)0x1C,(byte)0xA0,(byte)0xB1,(byte)0xBC,(byte)0xB4,(byte)0x99,(byte)0xBC,(byte)0xFB,(byte)0x60,(byte)0x48,(byte)0x45,(byte)0xFB,(byte)0x52,(byte)0x5A,(byte)0xE5,(byte)0x1A,(byte)0x43,(byte)0x6B,(byte)0x26,(byte)0xC3,(byte)0xD8,(byte)0xE6,(byte)0x1F,(byte)0x0F,(byte)0x1D,(byte)0x77,(byte)0x92,(byte)0xB7,(byte)0x05,(byte)0x15,(byte)0x8A,(byte)0xEE,(byte)0xB8,(byte)0x62,(byte)0x82,(byte)0x9D,(byte)0x98,(byte)0x94,(byte)0xA7,(byte)0xBA,(byte)0x7B,(byte)0x19,(byte)0x8B,(byte)0x8E,(byte)0x3F,(byte)0xB4,(byte)0x1B,(byte)0x9B,(byte)0x4D,(byte)0xD3,(byte)0xA2,(byte)0x28,(byte)0x05,(byte)0x99,(byte)0xC8,(byte)0xF7,(byte)0x2A,(byte)0x6F,(byte)0xB9,(byte)0xC9,(byte)0x96,(byte)0xF6,(byte)0x03,(byte)0xC6,(byte)0x10,(byte)0xBF,(byte)0xF2,(byte)0xD5,(byte)0xAE,(byte)0x7F,(byte)0x93,(byte)0xE4,(byte)0xB6,(byte)0x4D,(byte)0xE0,(byte)0xE5,(byte)0x06,(byte)0x4E,(byte)0x4C,(byte)0xC5,(byte)0xD5,(byte)0xD9,(byte)0xF8,(byte)0x1E,(byte)0x36,(byte)0x38,(byte)0x01,(byte)0x7C,(byte)0xBC,(byte)0x1C,(byte)0x71,(byte)0x46,(byte)0x2C,(byte)0xCE,(byte)0xBD,(byte)0x23,(byte)0x14,(byte)0x37,(byte)0xBB,(byte)0x70,(byte)0xC6,(byte)0x7A,(byte)0xF7,(byte)0x73,(byte)0xA8,(byte)0xA9,(byte)0xDC,(byte)0xC2,(byte)0xC0,(byte)0x7A,(byte)0xDA,(byte)0x74,(byte)0xFF,(byte)0x25,(byte)0x73,(byte)0x31,(byte)0xD8,(byte)0xF9,(byte)0x4D,(byte)0x66,(byte)0xD3,(byte)0x5E,(byte)0x98,(byte)0xC6,(byte)0xC4,(byte)0x55,(byte)0x0B,(byte)0xC4,(byte)0xB1,(byte)0xED,(byte)0x0F,(byte)0x74,(byte)0x5D,(byte)0x1B,(byte)0x7A,(byte)0x05,(byte)0xDB,(byte)0x7C,(byte)0x0D,(byte)0xDF,(byte)0xE2,(byte)0x6B,(byte)0xAF,(byte)0x22,(byte)0x3B,(byte)0x11,(byte)0x35,(byte)0xE3,(byte)0x51,(byte)0x13,(byte)0x07,(byte)0xD3,(byte)0x6E,(byte)0xAE,(byte)0x91,(byte)0xE2,(byte)0x98,(byte)0x02,(byte)0x6D,(byte)0xD9,(byte)0xD9,(byte)0xBD,(byte)0x7C,(byte)0x8E,(byte)0xBF,(byte)0xBE,(byte)0xB7,(byte)0x79,(byte)0xCA,(byte)0xC1,(byte)0x66,(byte)0x89,(byte)0x17,(byte)0x9B,(byte)0x77,(byte)0xBE,(byte)0xA7,(byte)0xED,(byte)0x3E,(byte)0xCC,(byte)0x86,(byte)0x44,(byte)0x42,(byte)0x38,(byte)0x50,(byte)0x8D,(byte)0xC3,(byte)0x58,(byte)0x07,(byte)0x42,(byte)0xBF,(byte)0x7C,(byte)0xC3,(byte)0x72,(byte)0x81,(byte)0x6E,(byte)0xFC,(byte)0xC8,(byte)0x63,(byte)0x8B,(byte)0x2E,(byte)0x63,(byte)0xA6,(byte)0x17,(byte)0x62,(byte)0x3C,(byte)0xED,(byte)0x29,(byte)0xFE,(byte)0xBC,(byte)0x4E,(byte)0x8B,(byte)0x94,(byte)0x4B,(byte)0x46,(byte)0xE6,(byte)0xC7,(byte)0x1A,(byte)0x32,(byte)0xE7,(byte)0xC8,(byte)0x44,(byte)0x47,(byte)0x1C,(byte)0xE8,(byte)0xC7,(byte)0x8C,(byte)0x1F,(byte)0x9E,(byte)0x16,(byte)0xED,(byte)0x12,(byte)0x8D,(byte)0x66,(byte)0x71,(byte)0xF4,(byte)0x1E,(byte)0x22,(byte)0xAB,(byte)0xD9,(byte)0xF5,(byte)0x22,(byte)0xC3,(byte)0x31,(byte)0x0B,(byte)0xD6,(byte)0x12,(byte)0x46,(byte)0x99,(byte)0x13,(byte)0xD2,(byte)0x02,(byte)0x34,(byte)0x7E,(byte)0x01,(byte)0x25,(byte)0xAC,(byte)0xB6,(byte)0xF1,(byte)0xF1,(byte)0x46,(byte)0xBE,(byte)0x90,(byte)0x79,(byte)0xBA,(byte)0x5B,(byte)0x36,(byte)0xF7,(byte)0x81,(byte)0x70,(byte)0x4A,(byte)0xDC,(byte)0xF1,(byte)0x24,(byte)0x9A,(byte)0x87,(byte)0x1E,(byte)0x59,(byte)0xE1,(byte)0x46,(byte)0xDC,(byte)0x0E,(byte)0x71,(byte)0xB4,(byte)0xE5,(byte)0x48,(byte)0x0E,(byte)0x11,(byte)0x87,(byte)0x99,(byte)0x2A,(byte)0x5C,(byte)0x61,(byte)0x75,(byte)0x3C,(byte)0x5B,(byte)0xF8,(byte)0xE6,(byte)0xE4,(byte)0x01,(byte)0xA2,(byte)0x01,(byte)0xE5,(byte)0x79,(byte)0x52,(byte)0x0B,(byte)0xC7,(byte)0xF7,(byte)0xED,(byte)0x0B,(byte)0x52,(byte)0x47,(byte)0x77,(byte)0xAD,(byte)0x45,(byte)0x72,(byte)0x21,(byte)0x0E,(byte)0xBE,(byte)0xA5,(byte)0x3D,(byte)0xEA,(byte)0xBF,(byte)0x44,(byte)0x7E,(byte)0x75,(byte)0x8C,(byte)0xF0,(byte)0x05,(byte)0xBB,(byte)0xDD,(byte)0xE3,(byte)0x53,(byte)0x4E,(byte)0x1B,(byte)0xB4,(byte)0x2F,(byte)0x65,(byte)0xCE,(byte)0x44,(byte)0x95,(byte)0x4F,(byte)0x44,(byte)0x1D,(byte)0x0D,(byte)0x54,(byte)0xF9,(byte)0xCD,(byte)0x30,(byte)0x00,(byte)0x81,(byte)0x2C,(byte)0x5C,(byte)0xFF,(byte)0xBE}; | |
| 158 | + H264Packeter packeter = new H264Packeter(); | |
| 159 | + packeter.packet(bytes, 0); | |
| 160 | + } | |
| 161 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/rtsp/RtspRequest.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/rtsp/RtspRequest.java | |
| 1 | +package cn.org.hentai.jtt1078.rtsp; | |
| 2 | + | |
| 3 | +import io.netty.handler.codec.http.DefaultFullHttpRequest; | |
| 4 | +import io.netty.handler.codec.http.FullHttpRequest; | |
| 5 | +import io.netty.handler.codec.rtsp.RtspHeaderNames; | |
| 6 | +import io.netty.handler.codec.rtsp.RtspMethods; | |
| 7 | +import io.netty.handler.codec.rtsp.RtspVersions; | |
| 8 | +import io.netty.util.internal.StringUtil; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * Rtsp请求 | |
| 12 | + */ | |
| 13 | +public class RtspRequest { | |
| 14 | + | |
| 15 | + private final String CRLF = "\r\n"; | |
| 16 | + | |
| 17 | + private final String VERSION = "RTSP/1.0"; | |
| 18 | + | |
| 19 | + private int seq = 0; | |
| 20 | + | |
| 21 | + private String host; | |
| 22 | + | |
| 23 | + private int port; | |
| 24 | + | |
| 25 | + private String path; | |
| 26 | + | |
| 27 | + private String sessionID; | |
| 28 | + | |
| 29 | + public RtspRequest(String host, int port, String path) { | |
| 30 | + this.host = host; | |
| 31 | + this.port = port; | |
| 32 | + this.path = path; | |
| 33 | + } | |
| 34 | + | |
| 35 | + public String getHost() { | |
| 36 | + return host; | |
| 37 | + } | |
| 38 | + | |
| 39 | + public void setHost(String host) { | |
| 40 | + this.host = host; | |
| 41 | + } | |
| 42 | + | |
| 43 | + public int getPort() { | |
| 44 | + return port; | |
| 45 | + } | |
| 46 | + | |
| 47 | + public void setPort(int port) { | |
| 48 | + this.port = port; | |
| 49 | + } | |
| 50 | + | |
| 51 | + public String getPath() { | |
| 52 | + return path; | |
| 53 | + } | |
| 54 | + | |
| 55 | + public void setPath(String path) { | |
| 56 | + this.path = path; | |
| 57 | + } | |
| 58 | + | |
| 59 | + public String getSessionID() { | |
| 60 | + return sessionID; | |
| 61 | + } | |
| 62 | + | |
| 63 | + public void setSessionID(String sessionID) { | |
| 64 | + this.sessionID = sessionID; | |
| 65 | + } | |
| 66 | + | |
| 67 | + public FullHttpRequest option() { | |
| 68 | + FullHttpRequest request = new DefaultFullHttpRequest(RtspVersions.RTSP_1_0, RtspMethods.OPTIONS, String.format("rtsp://%s:%d%s", host, port, path)); | |
| 69 | + request.headers().set(RtspHeaderNames.CSEQ, ++seq); | |
| 70 | + | |
| 71 | + return request; | |
| 72 | + } | |
| 73 | + | |
| 74 | + public FullHttpRequest announce() { | |
| 75 | + StringBuilder body = new StringBuilder(); | |
| 76 | + FullHttpRequest request = new DefaultFullHttpRequest(RtspVersions.RTSP_1_0, RtspMethods.ANNOUNCE, String.format("rtsp://%s:%d%s", host, port, path)); | |
| 77 | + request.headers().set(RtspHeaderNames.CSEQ, ++seq); | |
| 78 | + request.headers().set(RtspHeaderNames.CONTENT_TYPE, "application/sdp"); | |
| 79 | + | |
| 80 | + body.append("v=0").append(CRLF) | |
| 81 | + .append("o=- 0 0 IN IP4 127.0.0.1").append(CRLF) | |
| 82 | + .append("s=No Name").append(CRLF) | |
| 83 | + .append("c=IN IP4 ").append(this.host).append(CRLF) | |
| 84 | + .append("t=0 0").append(CRLF) | |
| 85 | + .append("a=tool:libavformat 61.9.100").append(CRLF) | |
| 86 | + .append("m=video 0 RTP/AVP 96").append(CRLF) | |
| 87 | + .append("b=AS:3943").append(CRLF) | |
| 88 | + .append("a=rtpmap:96 H264/90000").append(CRLF) | |
| 89 | + .append("a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAoAAADAAgAAAMBlCA=,aO48gA==; profile-level-id=4D002A").append(CRLF) | |
| 90 | + .append("a=control:streamid=0").append(CRLF); | |
| 91 | + request.content().writeBytes(body.toString().getBytes()); | |
| 92 | + request.headers().set(RtspHeaderNames.CONTENT_LENGTH, body.toString().getBytes().length); | |
| 93 | + | |
| 94 | + return request; | |
| 95 | + } | |
| 96 | + | |
| 97 | + public FullHttpRequest setup() { | |
| 98 | + FullHttpRequest request = new DefaultFullHttpRequest(RtspVersions.RTSP_1_0, RtspMethods.SETUP, String.format("rtsp://%s:%d%s/streamid=0", host, port, path)); | |
| 99 | + request.headers().set(RtspHeaderNames.CSEQ, ++seq); | |
| 100 | + request.headers().set(RtspHeaderNames.TRANSPORT, "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); | |
| 101 | + request.headers().set(RtspHeaderNames.SESSION, sessionID); | |
| 102 | + | |
| 103 | + return request; | |
| 104 | + } | |
| 105 | + | |
| 106 | + public FullHttpRequest record() { | |
| 107 | + FullHttpRequest request = new DefaultFullHttpRequest(RtspVersions.RTSP_1_0, RtspMethods.RECORD, String.format("rtsp://%s:%d%s/streamid=0", host, port, path)); | |
| 108 | + request.headers().set(RtspHeaderNames.CSEQ, ++seq); | |
| 109 | + request.headers().set(RtspHeaderNames.RANGE, "npt=0.000-"); | |
| 110 | + request.headers().set(RtspHeaderNames.SESSION, sessionID); | |
| 111 | + | |
| 112 | + return request; | |
| 113 | + } | |
| 114 | + | |
| 115 | + public String teardown() { | |
| 116 | + StringBuilder sb = new StringBuilder(); | |
| 117 | + | |
| 118 | + return sb.toString(); | |
| 119 | + } | |
| 120 | + | |
| 121 | + private String header(int length) { | |
| 122 | + StringBuilder sb = new StringBuilder(); | |
| 123 | + | |
| 124 | + return sb.append("CSeq: ").append(++seq).append(CRLF) | |
| 125 | + .append("User-Agent: jt1078").append(CRLF) | |
| 126 | + .append("Content-Length: ").append(length).append(CRLF) | |
| 127 | + .append(StringUtil.isNullOrEmpty(sessionID) ? "" : "Session: ").append(StringUtil.isNullOrEmpty(sessionID) ? "" : sessionID).append(StringUtil.isNullOrEmpty(sessionID) ? "" : CRLF).append(CRLF).toString(); | |
| 128 | + } | |
| 129 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/rtsp/RtspSessionManager.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/rtsp/RtspSessionManager.java | |
| 1 | +package cn.org.hentai.jtt1078.rtsp; | |
| 2 | + | |
| 3 | +import io.netty.channel.Channel; | |
| 4 | + | |
| 5 | +import java.util.Map; | |
| 6 | +import java.util.concurrent.ConcurrentHashMap; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * Rtsp会话 | |
| 10 | + */ | |
| 11 | +public class RtspSessionManager { | |
| 12 | + | |
| 13 | + private static Map<String, Object> channel2register = new ConcurrentHashMap<>(); | |
| 14 | + | |
| 15 | + private static Map<String, Channel> channel2push = new ConcurrentHashMap<>(); | |
| 16 | + | |
| 17 | + public static void register(String channel) { | |
| 18 | + channel2register.put(channel, System.currentTimeMillis()); | |
| 19 | + } | |
| 20 | + | |
| 21 | + public static void unregister(String channel) { | |
| 22 | + channel2register.remove(channel); | |
| 23 | + } | |
| 24 | + | |
| 25 | + public static boolean isRegistered(String channel) { | |
| 26 | + return channel2register.containsKey(channel); | |
| 27 | + } | |
| 28 | + | |
| 29 | + public static void setPush(String channel, Channel push) { | |
| 30 | + channel2push.put(channel, push); | |
| 31 | + } | |
| 32 | + | |
| 33 | + public static Channel getPush(String channel) { | |
| 34 | + return channel2push.get(channel); | |
| 35 | + } | |
| 36 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/server/Jtt1078Decoder.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/server/Jtt1078Decoder.java | |
| 1 | +package cn.org.hentai.jtt1078.server; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.util.ByteHolder; | |
| 4 | +import cn.org.hentai.jtt1078.util.ByteUtils; | |
| 5 | +import cn.org.hentai.jtt1078.util.Packet; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Created by matrixy on 2019/4/9. | |
| 9 | + */ | |
| 10 | +public class Jtt1078Decoder | |
| 11 | +{ | |
| 12 | + ByteHolder buffer = new ByteHolder(4096); | |
| 13 | + | |
| 14 | + public void write(byte[] block) | |
| 15 | + { | |
| 16 | + buffer.write(block); | |
| 17 | + } | |
| 18 | + | |
| 19 | + public void write(byte[] block, int startIndex, int length) | |
| 20 | + { | |
| 21 | + byte[] buff = new byte[length]; | |
| 22 | + System.arraycopy(block, startIndex, buff, 0, length); | |
| 23 | + write(buff); | |
| 24 | + } | |
| 25 | + | |
| 26 | + public Packet decode() | |
| 27 | + { | |
| 28 | + if (this.buffer.size() < 30) return null; | |
| 29 | + | |
| 30 | + if ((buffer.getInt(0) & 0x7fffffff) != 0x30316364) | |
| 31 | + { | |
| 32 | + String header = ByteUtils.toString(buffer.array(30)); | |
| 33 | + throw new RuntimeException("invalid protocol header: " + header); | |
| 34 | + } | |
| 35 | + | |
| 36 | + int lengthOffset = 28; | |
| 37 | + int dataType = (this.buffer.get(15) >> 4) & 0x0f; | |
| 38 | + // 透传数据类型:0100,没有后面的时间以及Last I Frame Interval和Last Frame Interval字段 | |
| 39 | + if (dataType == 0x04) lengthOffset = 28 - 8 - 2 - 2; | |
| 40 | + else if (dataType == 0x03) lengthOffset = 28 - 4; | |
| 41 | + int bodyLength = this.buffer.getShort(lengthOffset); | |
| 42 | + | |
| 43 | + int packetLength = bodyLength + lengthOffset + 2; | |
| 44 | + | |
| 45 | + if (this.buffer.size() < packetLength) return null; | |
| 46 | + byte[] block = new byte[packetLength]; | |
| 47 | + this.buffer.sliceInto(block, packetLength); | |
| 48 | + return Packet.create(block); | |
| 49 | + } | |
| 50 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/server/Jtt1078Handler.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/server/Jtt1078Handler.java | |
| 1 | +package cn.org.hentai.jtt1078.server; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.publisher.Channel; | |
| 4 | +import cn.org.hentai.jtt1078.publisher.PublishManager; | |
| 5 | +import cn.org.hentai.jtt1078.rtp.H264Packeter; | |
| 6 | +import cn.org.hentai.jtt1078.rtsp.RtspSessionManager; | |
| 7 | +import cn.org.hentai.jtt1078.util.Packet; | |
| 8 | +import io.netty.bootstrap.Bootstrap; | |
| 9 | +import io.netty.buffer.Unpooled; | |
| 10 | +import io.netty.channel.*; | |
| 11 | +import io.netty.channel.nio.NioEventLoopGroup; | |
| 12 | +import io.netty.channel.socket.SocketChannel; | |
| 13 | +import io.netty.channel.socket.nio.NioSocketChannel; | |
| 14 | +import io.netty.handler.codec.rtsp.RtspDecoder; | |
| 15 | +import io.netty.handler.codec.rtsp.RtspEncoder; | |
| 16 | +import io.netty.util.AttributeKey; | |
| 17 | +import org.slf4j.Logger; | |
| 18 | +import org.slf4j.LoggerFactory; | |
| 19 | +import io.netty.handler.timeout.IdleState; | |
| 20 | +import io.netty.handler.timeout.IdleStateEvent; | |
| 21 | + | |
| 22 | +import java.net.InetSocketAddress; | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * Created by matrixy on 2019/4/9. | |
| 26 | + */ | |
| 27 | +public class Jtt1078Handler extends SimpleChannelInboundHandler<Packet> | |
| 28 | +{ | |
| 29 | + static Logger logger = LoggerFactory.getLogger(Jtt1078Handler.class); | |
| 30 | + private static final AttributeKey<Session> SESSION_KEY = AttributeKey.valueOf("session-key"); | |
| 31 | + | |
| 32 | + @Override | |
| 33 | + protected void channelRead0(ChannelHandlerContext ctx, Packet packet) throws Exception | |
| 34 | + { | |
| 35 | + io.netty.channel.Channel nettyChannel = ctx.channel(); | |
| 36 | + | |
| 37 | + packet.seek(8); | |
| 38 | + String sim = packet.nextBCD() + packet.nextBCD() + packet.nextBCD() + packet.nextBCD() + packet.nextBCD() + packet.nextBCD(); | |
| 39 | + int channel = packet.nextByte() & 0xff; | |
| 40 | + String tag = sim + "-" + channel; | |
| 41 | + | |
| 42 | + if (SessionManager.contains(nettyChannel, "tag") == false) { | |
| 43 | + Channel chl = PublishManager.getInstance().open(tag); | |
| 44 | + SessionManager.set(nettyChannel, "tag", tag); | |
| 45 | + new Thread(new PushTask(tag)).start(); | |
| 46 | + logger.info("start publishing: {} -> {}-{}", Long.toHexString(chl.hashCode() & 0xffffffffL), sim, channel); | |
| 47 | + } | |
| 48 | + | |
| 49 | + Integer sequence = SessionManager.get(nettyChannel, "video-sequence"); | |
| 50 | + if (sequence == null) sequence = 0; | |
| 51 | + // 1. 做好序号 | |
| 52 | + // 2. 音频需要转码后提供订阅 | |
| 53 | + int lengthOffset = 28; | |
| 54 | + int dataType = (packet.seek(15).nextByte() >> 4) & 0x0f; | |
| 55 | + int pkType = packet.seek(15).nextByte() & 0x0f; | |
| 56 | + // 透传数据类型:0100,没有后面的时间以及Last I Frame Interval和Last Frame Interval字段 | |
| 57 | + if (dataType == 0x04) lengthOffset = 28 - 8 - 2 - 2; | |
| 58 | + else if (dataType == 0x03) lengthOffset = 28 - 4; | |
| 59 | + | |
| 60 | + int pt = packet.seek(5).nextByte() & 0x7f; | |
| 61 | + | |
| 62 | + if (dataType == 0x00 || dataType == 0x01 || dataType == 0x02) | |
| 63 | + { | |
| 64 | + // 碰到结束标记时,序号+1 | |
| 65 | + if (pkType == 0 || pkType == 2) | |
| 66 | + { | |
| 67 | + sequence += 1; | |
| 68 | + SessionManager.set(nettyChannel, "video-sequence", sequence); | |
| 69 | + } | |
| 70 | + long timestamp = packet.seek(16).nextLong(); | |
| 71 | + //PublishManager.getInstance().publishVideo(tag, sequence, timestamp, pt, packet.seek(lengthOffset + 2).nextBytes()); | |
| 72 | + if (RtspSessionManager.isRegistered(tag)) { | |
| 73 | + io.netty.channel.Channel push = RtspSessionManager.getPush(tag); | |
| 74 | + H264Packeter packeter = (H264Packeter) push.attr(AttributeKey.valueOf(tag)).get(); | |
| 75 | + for (byte[] nalu : packeter.packet(packet.seek(lengthOffset + 2).nextBytes(), timestamp)) { | |
| 76 | + push.writeAndFlush(Unpooled.wrappedBuffer(nalu)); | |
| 77 | + } | |
| 78 | + } | |
| 79 | + } | |
| 80 | + else if (dataType == 0x03) | |
| 81 | + { | |
| 82 | + long timestamp = packet.seek(16).nextLong(); | |
| 83 | + byte[] data = packet.seek(lengthOffset + 2).nextBytes(); | |
| 84 | + PublishManager.getInstance().publishAudio(tag, sequence, timestamp, pt, data); | |
| 85 | + } | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public void channelInactive(ChannelHandlerContext ctx) throws Exception | |
| 90 | + { | |
| 91 | + super.channelInactive(ctx); | |
| 92 | + release(ctx.channel()); | |
| 93 | + } | |
| 94 | + | |
| 95 | + @Override | |
| 96 | + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception | |
| 97 | + { | |
| 98 | + // super.exceptionCaught(ctx, cause); | |
| 99 | + cause.printStackTrace(); | |
| 100 | + release(ctx.channel()); | |
| 101 | + ctx.close(); | |
| 102 | + } | |
| 103 | + | |
| 104 | + @Override | |
| 105 | + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { | |
| 106 | + if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) { | |
| 107 | + IdleStateEvent event = (IdleStateEvent) evt; | |
| 108 | + if (event.state() == IdleState.READER_IDLE) { | |
| 109 | + String tag = SessionManager.get(ctx.channel(), "tag"); | |
| 110 | + logger.info("read timeout: {}",tag); | |
| 111 | + release(ctx.channel()); | |
| 112 | + } | |
| 113 | + } | |
| 114 | + } | |
| 115 | + | |
| 116 | + private void release(io.netty.channel.Channel channel) | |
| 117 | + { | |
| 118 | + String tag = SessionManager.get(channel, "tag"); | |
| 119 | + if (tag != null) | |
| 120 | + { | |
| 121 | + logger.info("close netty channel: {}", tag); | |
| 122 | + PublishManager.getInstance().close(tag); | |
| 123 | + } | |
| 124 | + } | |
| 125 | + | |
| 126 | + static class PushTask implements Runnable { | |
| 127 | + | |
| 128 | + private String channelId; | |
| 129 | + | |
| 130 | + private H264Packeter packeter = new H264Packeter(); | |
| 131 | + | |
| 132 | + public PushTask(String channelId) { | |
| 133 | + this.channelId = channelId; | |
| 134 | + } | |
| 135 | + | |
| 136 | + @Override | |
| 137 | + public void run() { | |
| 138 | + EventLoopGroup group = new NioEventLoopGroup(); | |
| 139 | + try { | |
| 140 | + ChannelFuture future = null; | |
| 141 | + Bootstrap clientBootstrap = new Bootstrap(); | |
| 142 | + clientBootstrap.group(group) | |
| 143 | + .option(ChannelOption.TCP_NODELAY, true) | |
| 144 | + .channel(NioSocketChannel.class) | |
| 145 | + .remoteAddress(new InetSocketAddress("192.168.169.100", 9555)) | |
| 146 | + .handler(new ChannelInitializer<SocketChannel>() { | |
| 147 | + @Override | |
| 148 | + protected void initChannel(SocketChannel socketChannel) { | |
| 149 | + socketChannel.pipeline() | |
| 150 | + .addLast(new RtspEncoder()) | |
| 151 | + .addLast(new RtspDecoder()) | |
| 152 | + .addLast(new RTSPHandler()); | |
| 153 | + } | |
| 154 | + }); | |
| 155 | + while (true) { | |
| 156 | + logger.info("Waiting for server connection"); | |
| 157 | + future = clientBootstrap.connect(); | |
| 158 | + future.awaitUninterruptibly(); | |
| 159 | + if (future.isSuccess()) { | |
| 160 | + logger.info("RTSP Connection success!"); | |
| 161 | + break; | |
| 162 | + } | |
| 163 | + Thread.sleep(1000); | |
| 164 | + } | |
| 165 | + | |
| 166 | + future.channel().attr(AttributeKey.valueOf(this.channelId)).set(this.packeter); | |
| 167 | + RtspSessionManager.setPush(this.channelId, future.channel()); | |
| 168 | + // Wait for the server to close the connection. | |
| 169 | + //future.channel().closeFuture().sync(); | |
| 170 | + } catch (Exception e) { | |
| 171 | + logger.error("Error ->", e); | |
| 172 | + logger.error("<- Error"); | |
| 173 | + } | |
| 174 | + } | |
| 175 | + } | |
| 176 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/server/Jtt1078MessageDecoder.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/server/Jtt1078MessageDecoder.java | |
| 1 | +package cn.org.hentai.jtt1078.server; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.util.ByteUtils; | |
| 4 | +import cn.org.hentai.jtt1078.util.Packet; | |
| 5 | +import io.netty.buffer.ByteBuf; | |
| 6 | +import io.netty.channel.ChannelHandlerContext; | |
| 7 | +import io.netty.handler.codec.ByteToMessageDecoder; | |
| 8 | +import org.slf4j.Logger; | |
| 9 | +import org.slf4j.LoggerFactory; | |
| 10 | + | |
| 11 | +import java.util.List; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * Created by matrixy on 2019/4/9. | |
| 15 | + */ | |
| 16 | +public class Jtt1078MessageDecoder extends ByteToMessageDecoder | |
| 17 | +{ | |
| 18 | + static Logger logger = LoggerFactory.getLogger(Jtt1078MessageDecoder.class); | |
| 19 | + byte[] block = new byte[4096]; | |
| 20 | + Jtt1078Decoder decoder = new Jtt1078Decoder(); | |
| 21 | + | |
| 22 | + @Override | |
| 23 | + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception | |
| 24 | + { | |
| 25 | + int length = in.readableBytes(); | |
| 26 | + while (length > 30) { | |
| 27 | + in.markReaderIndex(); | |
| 28 | + int frameHeader = in.readInt(); | |
| 29 | + if (frameHeader == 0x30316364) { | |
| 30 | + in.skipBytes(11); | |
| 31 | + int dataType = (in.readByte() >> 4) & 0xFF, lengthOffset = 28; | |
| 32 | + // 透传数据类型:0100,没有后面的时间以及Last I Frame Interval和Last Frame Interval字段 | |
| 33 | + if (dataType == 0x04) lengthOffset = 28 - 8 - 2 - 2; | |
| 34 | + else if (dataType == 0x03) lengthOffset = 28 - 4; | |
| 35 | + in.skipBytes(lengthOffset - 16); | |
| 36 | + int bodyLength = in.readShort() & 0xFFFFFFFF; | |
| 37 | + int packetLength = lengthOffset + bodyLength + 2; | |
| 38 | + //System.out.println(bodyLength); | |
| 39 | + if (length < packetLength) { | |
| 40 | + in.resetReaderIndex(); | |
| 41 | + return; | |
| 42 | + } | |
| 43 | + byte[] data = new byte[packetLength]; | |
| 44 | + in.resetReaderIndex(); | |
| 45 | + in.readBytes(data); | |
| 46 | + Packet packet = Packet.create(data); | |
| 47 | + out.add(packet); | |
| 48 | +// System.out.println("start:" + (lengthOffset + 2)); | |
| 49 | +// System.out.println(toHex(data)); | |
| 50 | + } else { | |
| 51 | + in.resetReaderIndex(); | |
| 52 | + in.readByte(); | |
| 53 | + } | |
| 54 | + | |
| 55 | + length = in.readableBytes(); | |
| 56 | + } | |
| 57 | + } | |
| 58 | + | |
| 59 | + public String toHex(byte[] bytes) { | |
| 60 | + StringBuilder sb = new StringBuilder(); | |
| 61 | + for (byte b : bytes) { | |
| 62 | + sb.append(String.format("%02X ", b)); | |
| 63 | + } | |
| 64 | + | |
| 65 | + return sb.toString(); | |
| 66 | + } | |
| 67 | +} | ... | ... |
src/main/java/cn/org/hentai/jtt1078/server/RTSPHandler.java
0 → 100644
| 1 | +++ a/src/main/java/cn/org/hentai/jtt1078/server/RTSPHandler.java | |
| 1 | +package cn.org.hentai.jtt1078.server; | |
| 2 | + | |
| 3 | +import cn.org.hentai.jtt1078.rtsp.RtspRequest; | |
| 4 | +import cn.org.hentai.jtt1078.rtsp.RtspSessionManager; | |
| 5 | +import io.netty.bootstrap.Bootstrap; | |
| 6 | +import io.netty.buffer.ByteBuf; | |
| 7 | +import io.netty.channel.*; | |
| 8 | +import io.netty.channel.nio.NioEventLoopGroup; | |
| 9 | +import io.netty.channel.socket.SocketChannel; | |
| 10 | +import io.netty.channel.socket.nio.NioSocketChannel; | |
| 11 | +import io.netty.handler.codec.http.*; | |
| 12 | +import io.netty.handler.codec.rtsp.*; | |
| 13 | +import io.netty.util.internal.StringUtil; | |
| 14 | +import org.slf4j.Logger; | |
| 15 | +import org.slf4j.LoggerFactory; | |
| 16 | + | |
| 17 | +import java.net.InetSocketAddress; | |
| 18 | +import java.util.*; | |
| 19 | + | |
| 20 | +public class RTSPHandler extends ChannelInboundHandlerAdapter { | |
| 21 | + | |
| 22 | + private final static Logger log = LoggerFactory.getLogger(RTSPHandler.class); | |
| 23 | + | |
| 24 | + private String channelId = "138000099998-1"; | |
| 25 | + | |
| 26 | + private HttpMethod currentMethod = RtspMethods.OPTIONS; | |
| 27 | + | |
| 28 | + private List<HttpMethod> methods = Arrays.asList(RtspMethods.OPTIONS, RtspMethods.ANNOUNCE, RtspMethods.SETUP, RtspMethods.RECORD, null); | |
| 29 | + | |
| 30 | + private RtspRequest rtspRequest = new RtspRequest("192.168.169.100", 9555, String.format("/schedule/%s?sign=41db35390ddad33f83944f44b8b75ded", channelId)); | |
| 31 | + | |
| 32 | + /* | |
| 33 | + When notified that the channel is active, sends a message. A channel is active | |
| 34 | + when a connection has been established, so the method is invoked when the connections | |
| 35 | + is established. | |
| 36 | + */ | |
| 37 | + @Override | |
| 38 | + public void channelActive(final ChannelHandlerContext ctx) { | |
| 39 | + log.debug("channelActive, connection established: {}", ctx); | |
| 40 | + log.debug("Sending request to the server"); | |
| 41 | + ctx.writeAndFlush(rtspRequest.option()); | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { | |
| 46 | + log.info("exceptionCaught: {}", cause); | |
| 47 | + ctx.close(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + @Override | |
| 51 | + public void channelRead(ChannelHandlerContext ctx, Object msg) { | |
| 52 | + log.info("Received from RTSP Server: {}", ctx); | |
| 53 | + log.info("Received from RTSP msg: {}", msg.toString()); | |
| 54 | + log.debug("Received Class Type: {}", msg.getClass().getTypeName()); | |
| 55 | + if (msg instanceof DefaultHttpResponse) { | |
| 56 | + DefaultHttpResponse res = (DefaultHttpResponse) msg; | |
| 57 | + if (RtspResponseStatuses.OK.equals(res.status())) { | |
| 58 | + log.debug("{}: {}", currentMethod, res.status()); | |
| 59 | + if (res.headers().contains(RtspHeaderNames.SESSION)) { | |
| 60 | + rtspRequest.setSessionID(res.headers().get(RtspHeaderNames.SESSION)); | |
| 61 | + } | |
| 62 | + nextMethod(ctx); | |
| 63 | + } else { | |
| 64 | + ctx.close(); | |
| 65 | + } | |
| 66 | + } else if (msg instanceof DefaultHttpContent) { | |
| 67 | + DefaultHttpContent content = (DefaultHttpContent) msg; | |
| 68 | + log.info("Content: {}", content); | |
| 69 | + | |
| 70 | + ByteBuf byteBuf = content.content(); | |
| 71 | + } else { | |
| 72 | + log.debug("dataType error: {}", msg.getClass().getTypeName()); | |
| 73 | + } | |
| 74 | + } | |
| 75 | + | |
| 76 | + private void nextMethod(ChannelHandlerContext ctx) { | |
| 77 | + for (int i = 0;i < methods.size(); i++) { | |
| 78 | + if (methods.get(i).equals(currentMethod) && i < methods.size() - 1) { | |
| 79 | + currentMethod = methods.get(i + 1); | |
| 80 | + break; | |
| 81 | + } | |
| 82 | + } | |
| 83 | + if (currentMethod == null) { | |
| 84 | + RtspSessionManager.register(channelId); | |
| 85 | + return; | |
| 86 | + } | |
| 87 | + if (currentMethod == RtspMethods.ANNOUNCE) { | |
| 88 | + ctx.writeAndFlush(rtspRequest.announce()); | |
| 89 | + } | |
| 90 | + if (currentMethod == RtspMethods.SETUP) { | |
| 91 | + ctx.writeAndFlush(rtspRequest.setup()); | |
| 92 | + } | |
| 93 | + if (currentMethod == RtspMethods.RECORD) { | |
| 94 | + ctx.writeAndFlush(rtspRequest.record()); | |
| 95 | + } | |
| 96 | + } | |
| 97 | + | |
| 98 | + private void parseSdp(String sdp) { | |
| 99 | + log.debug("Parsing SDP: {}", sdp); | |
| 100 | + Map<String, List<String>> mediaMap = new HashMap<>(10); | |
| 101 | + String[] array = sdp.split("\\n"); | |
| 102 | + String mediaName = ""; | |
| 103 | + for (int i = 0; i < array.length; i++) { | |
| 104 | + String line = array[i]; | |
| 105 | + if (line.startsWith("m=")) { | |
| 106 | + mediaName = line.substring(line.indexOf("=") + 1, line.indexOf(" ")); | |
| 107 | + if (mediaName.equals("video") || mediaName.equals("audio")) { | |
| 108 | + mediaMap.put(mediaName, new ArrayList<>()); | |
| 109 | + } else { | |
| 110 | + mediaName = ""; | |
| 111 | + } | |
| 112 | + } else if (!StringUtil.isNullOrEmpty(mediaName)) { | |
| 113 | + if (line.startsWith("b=") || line.startsWith("a=")) { | |
| 114 | + List<String> medialist = mediaMap.get(mediaName); | |
| 115 | + medialist.add(line); | |
| 116 | + } | |
| 117 | + } | |
| 118 | + } | |
| 119 | + for (String mediaKey : mediaMap.keySet()) { | |
| 120 | + StringBuilder stringBuilder = new StringBuilder(); | |
| 121 | + List<String> mediaInfo = mediaMap.get(mediaKey); | |
| 122 | + mediaInfo.forEach((s) -> { | |
| 123 | + stringBuilder.append("\n"); | |
| 124 | + stringBuilder.append(s); | |
| 125 | + }); | |
| 126 | + log.info("[>>>>> {} <<<<<] {}", mediaKey, stringBuilder.toString()); | |
| 127 | + } | |
| 128 | + } | |
| 129 | + | |
| 130 | + public static void main(String[] args) { | |
| 131 | + EventLoopGroup group = new NioEventLoopGroup(); | |
| 132 | + try { | |
| 133 | + Bootstrap clientBootstrap = new Bootstrap(); | |
| 134 | + clientBootstrap.group(group) | |
| 135 | + .option(ChannelOption.TCP_NODELAY, true) | |
| 136 | + .channel(NioSocketChannel.class) | |
| 137 | + .remoteAddress(new InetSocketAddress("192.168.169.100", 9555)) | |
| 138 | + .handler(new ChannelInitializer<SocketChannel>() { | |
| 139 | + @Override | |
| 140 | + protected void initChannel(SocketChannel socketChannel) { | |
| 141 | + socketChannel.pipeline() | |
| 142 | + .addLast(new RtspEncoder()) | |
| 143 | + .addLast(new RtspDecoder()) | |
| 144 | + .addLast(new RTSPHandler()); | |
| 145 | + } | |
| 146 | + }); | |
| 147 | + ChannelFuture f = null; | |
| 148 | + while (true) { | |
| 149 | + log.info("Waiting for server connection"); | |
| 150 | + f = clientBootstrap.connect(); | |
| 151 | + f.awaitUninterruptibly(); | |
| 152 | + if (f.isSuccess()) { | |
| 153 | + log.info("RTSP Connection success!"); | |
| 154 | + break; | |
| 155 | + } | |
| 156 | + Thread.sleep(1000); | |
| 157 | + } | |
| 158 | + | |
| 159 | + // Wait for the server to close the connection. | |
| 160 | + f.channel().closeFuture().sync(); | |
| 161 | + } catch (Exception e) { | |
| 162 | + log.error("Error ->", e); | |
| 163 | + log.error("<- Error"); | |
| 164 | + } finally { | |
| 165 | + // Shut down executor threads to exit.Ahkk | |
| 166 | + try { | |
| 167 | + log.info("ShutdownGracefully the connection group"); | |
| 168 | + group.shutdownGracefully().sync(); | |
| 169 | + } catch (InterruptedException e) { | |
| 170 | + log.error("", e); | |
| 171 | + } | |
| 172 | + } | |
| 173 | + } | |
| 174 | +} | |
| 0 | 175 | \ No newline at end of file | ... | ... |