Skip to content

Commit 145199c

Browse files
committed
audio decryption
1 parent f347955 commit 145199c

12 files changed

Lines changed: 233 additions & 134 deletions

File tree

build.gradle

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group 'com.github.serezhka'
7-
version '1.0.2'
7+
version '1.0.3'
88

99
sourceCompatibility = JavaVersion.VERSION_11
1010
targetCompatibility = JavaVersion.VERSION_11
@@ -18,9 +18,10 @@ repositories {
1818
}
1919

2020
dependencies {
21-
implementation "io.netty:netty-all:4.1.42.Final"
21+
implementation "io.netty:netty-all:4.1.52.Final"
2222
implementation "org.slf4j:slf4j-api:1.7.30"
23-
implementation "com.github.serezhka:java-airplay-lib:1.0.2"
23+
implementation "com.github.serezhka:java-airplay-lib:1.0.3"
24+
// implementation files('C:\\projects\\java-airplay-lib\\build\\libs\\java-airplay-lib-1.0.3-all.jar')
2425

2526
testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2"
2627
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.4.2"

src/main/java/com/github/serezhka/jap2server/AirPlayServer.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@
66
public class AirPlayServer {
77

88
private final AirPlayBonjour airPlayBonjour;
9-
private final MirrorDataConsumer mirrorDataConsumer;
9+
private final AirplayDataConsumer airplayDataConsumer;
1010
private final ControlServer controlServer;
1111

1212
private final String serverName;
1313
private final int airPlayPort;
1414
private final int airTunesPort;
1515

1616
public AirPlayServer(String serverName, int airPlayPort, int airTunesPort,
17-
MirrorDataConsumer mirrorDataConsumer) {
17+
AirplayDataConsumer airplayDataConsumer) {
1818
this.serverName = serverName;
1919
airPlayBonjour = new AirPlayBonjour(serverName);
2020
this.airPlayPort = airPlayPort;
2121
this.airTunesPort = airTunesPort;
22-
this.mirrorDataConsumer = mirrorDataConsumer;
23-
controlServer = new ControlServer(airPlayPort, airTunesPort, mirrorDataConsumer);
22+
this.airplayDataConsumer = airplayDataConsumer;
23+
controlServer = new ControlServer(airPlayPort, airTunesPort, airplayDataConsumer);
2424
}
2525

2626
public void start() throws Exception {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.github.serezhka.jap2server;
2+
3+
public interface AirplayDataConsumer {
4+
5+
void onVideo(byte[] video);
6+
7+
void onAudio(byte[] audio);
8+
}

src/main/java/com/github/serezhka/jap2server/MirrorDataConsumer.java

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/main/java/com/github/serezhka/jap2server/internal/AudioReceiver.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.github.serezhka.jap2server.internal.handler.audio.AudioHandler;
44
import io.netty.bootstrap.Bootstrap;
55
import io.netty.channel.ChannelInitializer;
6+
import io.netty.channel.ChannelOption;
67
import io.netty.channel.EventLoopGroup;
78
import io.netty.channel.epoll.Epoll;
89
import io.netty.channel.epoll.EpollDatagramChannel;
@@ -36,6 +37,7 @@ public void run() {
3637
.group(workerGroup)
3738
.channel(datagramChannelClass())
3839
.localAddress(new InetSocketAddress(port))
40+
.option(ChannelOption.SO_KEEPALIVE, true)
3941
.handler(new ChannelInitializer<DatagramChannel>() {
4042
@Override
4143
public void initChannel(final DatagramChannel ch) {

src/main/java/com/github/serezhka/jap2server/internal/ControlServer.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.github.serezhka.jap2server.internal;
22

3-
import com.github.serezhka.jap2server.MirrorDataConsumer;
3+
import com.github.serezhka.jap2server.AirplayDataConsumer;
44
import com.github.serezhka.jap2server.internal.handler.control.FairPlayHandler;
55
import com.github.serezhka.jap2server.internal.handler.control.HeartBeatHandler;
66
import com.github.serezhka.jap2server.internal.handler.control.PairingHandler;
@@ -39,12 +39,12 @@ public class ControlServer implements Runnable {
3939

4040
private final int airTunesPort;
4141

42-
public ControlServer(int airPlayPort, int airTunesPort, MirrorDataConsumer mirrorDataConsumer) {
42+
public ControlServer(int airPlayPort, int airTunesPort, AirplayDataConsumer airplayDataConsumer) {
4343
this.airTunesPort = airTunesPort;
4444
SessionManager sessionManager = new SessionManager();
4545
pairingHandler = new PairingHandler(sessionManager);
4646
fairPlayHandler = new FairPlayHandler(sessionManager);
47-
rtspHandler = new RTSPHandler(airPlayPort, airTunesPort, sessionManager, mirrorDataConsumer);
47+
rtspHandler = new RTSPHandler(airPlayPort, airTunesPort, sessionManager, airplayDataConsumer);
4848
heartBeatHandler = new HeartBeatHandler(sessionManager);
4949
}
5050

@@ -65,7 +65,7 @@ public void initChannel(final SocketChannel ch) throws Exception {
6565
new RtspDecoder(),
6666
new RtspEncoder(),
6767
new HttpObjectAggregator(64 * 1024),
68-
new LoggingHandler(LogLevel.INFO),
68+
new LoggingHandler(LogLevel.DEBUG),
6969
pairingHandler,
7070
fairPlayHandler,
7171
rtspHandler,

src/main/java/com/github/serezhka/jap2server/internal/MirroringReceiver.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ public void initChannel(final SocketChannel ch) {
4949
.childOption(ChannelOption.SO_REUSEADDR, true)
5050
.childOption(ChannelOption.SO_KEEPALIVE, true);
5151
var channelFuture = serverBootstrap.bind().sync();
52-
log.info("AirPlay receiver listening on port: {}", port);
52+
log.info("Mirroring receiver listening on port: {}", port);
5353
channelFuture.channel().closeFuture().sync();
5454
} catch (InterruptedException e) {
55-
log.info("AirPlay receiver interrupted");
55+
log.info("Mirroring receiver interrupted");
5656
} finally {
57-
log.info("AirPlay receiver stopped");
57+
log.info("Mirroring receiver stopped");
5858
bossGroup.shutdownGracefully();
5959
workerGroup.shutdownGracefully();
6060
}
Lines changed: 52 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,91 @@
11
package com.github.serezhka.jap2server.internal.handler.audio;
22

33
import com.github.serezhka.jap2lib.AirPlay;
4+
import com.github.serezhka.jap2server.AirplayDataConsumer;
45
import io.netty.buffer.ByteBuf;
56
import io.netty.channel.ChannelHandlerContext;
67
import io.netty.channel.SimpleChannelInboundHandler;
78
import io.netty.channel.socket.DatagramPacket;
89
import org.slf4j.Logger;
910
import org.slf4j.LoggerFactory;
1011

11-
import java.io.IOException;
12-
import java.nio.ByteBuffer;
13-
import java.nio.channels.FileChannel;
14-
import java.nio.file.Paths;
15-
import java.nio.file.StandardOpenOption;
16-
import java.util.Map;
17-
import java.util.TreeMap;
12+
import java.util.Arrays;
1813

1914
public class AudioHandler extends SimpleChannelInboundHandler<DatagramPacket> {
2015

2116
private static final Logger log = LoggerFactory.getLogger(AudioHandler.class);
2217

23-
private static boolean aacDecoderInitialized = false;
24-
25-
static {
26-
try {
27-
System.loadLibrary("libfdk-aac");
28-
aacDecoderInitialized = true;
29-
} catch (UnsatisfiedLinkError e) {
30-
log.warn("libfdk-aac.dll not found!");
31-
}
32-
}
33-
3418
private final AirPlay airPlay;
19+
private final AirplayDataConsumer dataConsumer;
3520

36-
private final Map<Integer, byte[]> buffer = new TreeMap<>();
21+
private final AudioPacket[] buffer = new AudioPacket[512];
3722

38-
private FileChannel fc;
3923
private int prevSeqNum;
40-
private boolean synced;
24+
private int packetsInBuffer;
4125

42-
public AudioHandler(AirPlay airPlay) throws IOException {
26+
public AudioHandler(AirPlay airPlay, AirplayDataConsumer dataConsumer) {
4327
this.airPlay = airPlay;
44-
if (aacDecoderInitialized) {
45-
fc = FileChannel.open(Paths.get("audio.pcm"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
46-
init();
28+
this.dataConsumer = dataConsumer;
29+
for (int i = 0; i < buffer.length; i++) {
30+
buffer[i] = new AudioPacket();
4731
}
4832
}
4933

50-
public native void init();
51-
52-
public native void decodeFrame(byte[] input, byte[] output);
53-
5434
@Override
5535
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
5636
ByteBuf content = msg.content();
57-
int contentLength = content.readableBytes();
58-
byte[] contentBytes = new byte[contentLength];
59-
content.readBytes(contentBytes);
60-
61-
int flag = contentBytes[0] & 0xFF;
62-
int type = contentBytes[1] & 0x7F;
6337

64-
// Version (V) is 2 and the payload type (PT) is 96 (DynamicRTP-Type-96).
65-
66-
log.debug("Got audio packet. flag: {}, type: {}, length: {}", flag, type, contentLength);
67-
68-
if (!aacDecoderInitialized) {
69-
return;
70-
}
71-
72-
if (type == 96 || type == 86) {
73-
int off = 0;
74-
if (type == 86) {
75-
off = 4;
76-
}
38+
byte[] headerBytes = new byte[12];
39+
content.readBytes(headerBytes);
7740

78-
int curSeqNo = ((contentBytes[off + 2] & 0xFF) << 8) | (contentBytes[off + 3] & 0xFF);
79-
//log.debug("Current sequence number: " + curSeqNo);
41+
int flag = headerBytes[0] & 0xFF;
42+
int type = headerBytes[1] & 0x7F;
8043

81-
long timestamp = (contentBytes[off + 7] & 0xFF) | ((contentBytes[off + 6] & 0xFF) << 8) | ((contentBytes[off + 5] & 0xFF) << 16) | ((contentBytes[off + 4] & 0xFF) << 24);
82-
//log.debug("Timestamp: " + timestamp);
44+
int curSeqNo = ((headerBytes[2] & 0xFF) << 8) | (headerBytes[3] & 0xFF);
8345

84-
long ssrc = (contentBytes[off + 11] & 0xFF) | ((contentBytes[off + 6] & 0xFF) << 8) | ((contentBytes[off + 9] & 0xFF) << 16) | ((contentBytes[off + 8] & 0xFF) << 24);
85-
//log.debug("SSRC: " + ssrc);
46+
long timestamp = (headerBytes[7] & 0xFF) | ((headerBytes[6] & 0xFF) << 8) |
47+
((headerBytes[5] & 0xFF) << 16) | ((headerBytes[4] & 0xFF) << 24);
8648

87-
if (contentLength > 16) {
88-
byte[] audio = new byte[contentLength - 12];
89-
System.arraycopy(contentBytes, 12, audio, 0, audio.length);
90-
airPlay.fairPlayDecryptAudioData(audio);
49+
long ssrc = (headerBytes[11] & 0xFF) | ((headerBytes[6] & 0xFF) << 8) |
50+
((headerBytes[9] & 0xFF) << 16) | ((headerBytes[8] & 0xFF) << 24);
9151

92-
if (prevSeqNum == 0) {
93-
prevSeqNum = curSeqNo;
94-
synced = true;
95-
96-
byte[] audioDecoded = new byte[480 * 4];
97-
decodeFrame(audio, audioDecoded);
98-
99-
fc.write(ByteBuffer.wrap(audioDecoded));
100-
} else {
101-
102-
if (curSeqNo - prevSeqNum == 1) {
103-
prevSeqNum = curSeqNo;
104-
synced = true;
105-
106-
byte[] audioDecoded = new byte[480 * 4];
107-
decodeFrame(audio, audioDecoded);
108-
109-
fc.write(ByteBuffer.wrap(audioDecoded));
110-
} else if (curSeqNo > prevSeqNum) {
111-
112-
synced = false;
113-
114-
byte[] audioDecoded = new byte[480 * 4];
115-
decodeFrame(audio, audioDecoded);
52+
// TODO handle bad cases (missing packets, curSeqNum - prevSeqNum > buffer.length, ...)
53+
if (curSeqNo <= prevSeqNum) {
54+
return;
55+
}
11656

117-
buffer.put(curSeqNo, audioDecoded);
118-
}
119-
}
120-
}
57+
log.debug("Got audio packet. flag: {}, type: {}, prevSeqNum: {}, curSecNum: {}, audio packets in buffer: {}",
58+
flag, type, prevSeqNum, curSeqNo, packetsInBuffer);
59+
60+
AudioPacket audioPacket = buffer[curSeqNo % buffer.length];
61+
audioPacket
62+
.flag(flag)
63+
.type(type)
64+
.sequenceNumber(curSeqNo)
65+
.timestamp(timestamp)
66+
.ssrc(ssrc)
67+
.available(true)
68+
.encodedAudioSize(content.readableBytes())
69+
.encodedAudio(packet -> content.readBytes(packet, 0, content.readableBytes()));
70+
packetsInBuffer++;
71+
72+
while (dequeue(curSeqNo)) {
73+
curSeqNo++;
74+
}
75+
}
12176

122-
if (!synced) {
123-
while (buffer.containsKey(prevSeqNum + 1)) {
124-
fc.write(ByteBuffer.wrap(buffer.remove(prevSeqNum + 1)));
125-
prevSeqNum++;
126-
}
127-
synced = buffer.isEmpty();
77+
private boolean dequeue(int curSeqNo) throws Exception {
78+
if (curSeqNo - prevSeqNum == 1 || prevSeqNum == 0) {
79+
AudioPacket audioPacket = buffer[curSeqNo % buffer.length];
80+
if (audioPacket.isAvailable()) {
81+
airPlay.decryptAudio(audioPacket.getEncodedAudio(), audioPacket.getEncodedAudioSize());
82+
dataConsumer.onAudio(Arrays.copyOfRange(audioPacket.getEncodedAudio(), 0, audioPacket.getEncodedAudioSize()));
83+
audioPacket.available(false);
84+
prevSeqNum = curSeqNo;
85+
packetsInBuffer--;
86+
return true;
12887
}
12988
}
89+
return false;
13090
}
13191
}

0 commit comments

Comments
 (0)