Commit 11a60adb533eaed6087324e4f7ebd03a22b8a1d1

Authored by 王通
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
  1 +++ a/.gitignore
  1 +/.idea/
  2 +/target/
  3 +/jtt1078-video-server.iml
0 \ No newline at end of file 4 \ No newline at end of file
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 \ No newline at end of file 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 \ No newline at end of file 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 \ No newline at end of file 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 \ No newline at end of file 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 \ No newline at end of file 175 \ No newline at end of file