Skip to content

Commit 9f78e76

Browse files
authored
New feature: Play Opus stream from camera (#11)
1 parent 4772b03 commit 9f78e76

24 files changed

+4169
-17
lines changed

README.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,17 @@ The project can then be opened in android studio and built from there.
3535

3636
## Installation
3737
- Download and install PixelPilot.apk from https://github.com/OpenIPC/PixelPilot/releases
38-
38+
- Audio feature: Now PixelPilot app had ability to play opus stream from majestic on camera. In order to enable this feature, pls enable on camera side:
39+
+ Audio settings in (/etc/majestic.yaml):
40+
```
41+
audio:
42+
enabled: true
43+
volume: 30
44+
srate: 8000
45+
codec: opus
46+
outputEnabled: false
47+
outputVolume: 30
48+
```
3949
## Tested devices based on real user reviews
4050

4151
* Samsung Galaxy A54 (Exynos 1380 processor)

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java

+2
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ protected void onStop() {
631631
unregisterReceivers();
632632
wfbLinkManager.stopAdapters();
633633
videoPlayer.stop();
634+
videoPlayer.stopAudio();
634635
super.onStop();
635636
}
636637

@@ -640,6 +641,7 @@ protected void onResume() {
640641

641642
// On resume is called when the app is reopened, a device might have been plugged since the last time it started.
642643
videoPlayer.start();
644+
videoPlayer.startAudio();
643645

644646
wfbLinkManager.setChannel(getChannel(this));
645647
wfbLinkManager.refreshAdapters();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// Created by Admin on 28/09/2024.
3+
//
4+
5+
#include "AudioDecoder.h"
6+
#include <android/log.h>
7+
8+
#define TAG "pixelpilot"
9+
10+
#define SAMPLE_RATE 8000
11+
#define CHANNELS 1
12+
13+
AudioDecoder::AudioDecoder() {
14+
initAudio();
15+
}
16+
17+
AudioDecoder::~AudioDecoder() {
18+
19+
stopAudioProcessing();
20+
delete pOpusDecoder;
21+
AAudioStream_requestStop(m_stream);
22+
AAudioStream_close(m_stream);
23+
}
24+
25+
void AudioDecoder::enqueueAudio(const uint8_t *data, const std::size_t data_length)
26+
{
27+
{
28+
std::lock_guard<std::mutex> lock(m_mtxQueue);
29+
m_audioQueue.push(AudioUDPPacket(data, data_length));
30+
}
31+
m_cvQueue.notify_one();
32+
}
33+
34+
void AudioDecoder::processAudioQueue() {
35+
while (true) {
36+
std::unique_lock<std::mutex> lock(m_mtxQueue);
37+
m_cvQueue.wait(lock, [this] { return !m_audioQueue.empty() || stopAudioFlag; });
38+
39+
if (stopAudioFlag) {
40+
break;
41+
}
42+
if (!m_audioQueue.empty()) {
43+
AudioUDPPacket audioPkt = m_audioQueue.front();
44+
onNewAudioData(audioPkt.data, audioPkt.len);
45+
m_audioQueue.pop();
46+
lock.unlock();
47+
}
48+
}
49+
}
50+
51+
void AudioDecoder::initAudio() {
52+
__android_log_print(ANDROID_LOG_DEBUG, TAG, "initAudio");
53+
int error;
54+
pOpusDecoder = opus_decoder_create(SAMPLE_RATE, CHANNELS, &error);
55+
// Create a stream m_builder
56+
AAudio_createStreamBuilder(&m_builder);
57+
58+
// Set the stream format
59+
AAudioStreamBuilder_setFormat(m_builder, AAUDIO_FORMAT_PCM_I16);
60+
AAudioStreamBuilder_setChannelCount(m_builder, CHANNELS); // Mono
61+
AAudioStreamBuilder_setSampleRate(m_builder, SAMPLE_RATE); // 8000 Hz
62+
63+
AAudioStreamBuilder_setBufferCapacityInFrames(m_builder, BUFFER_CAPACITY_IN_FRAMES);
64+
65+
// Open the stream
66+
AAudioStreamBuilder_openStream(m_builder, &m_stream);
67+
// Clean up the m_builder
68+
AAudioStreamBuilder_delete(m_builder);
69+
70+
AAudioStream_requestStart(m_stream);
71+
72+
isInit = true;
73+
}
74+
75+
void AudioDecoder::stopAudio()
76+
{
77+
__android_log_print(ANDROID_LOG_DEBUG, TAG, "stopAudio");
78+
AAudioStream_requestStop(m_stream);
79+
AAudioStream_close(m_stream);
80+
isInit = false;
81+
}
82+
83+
void AudioDecoder::onNewAudioData(const uint8_t *data, const std::size_t data_length) {
84+
const int rtp_header_size = 12;
85+
const uint8_t* opus_payload = data + rtp_header_size;
86+
int opus_payload_size = data_length - rtp_header_size;
87+
88+
int frame_size = opus_packet_get_samples_per_frame(opus_payload, SAMPLE_RATE);
89+
int nb_frames = opus_packet_get_nb_frames(opus_payload, opus_payload_size);
90+
91+
// Decode the frame
92+
int pcm_size = frame_size * nb_frames * CHANNELS;
93+
if(pOpusDecoder && m_stream) {
94+
opus_int16 pcm[pcm_size];
95+
int decoded_samples = opus_decode(pOpusDecoder, opus_payload, opus_payload_size, pcm,
96+
pcm_size, 0);
97+
98+
if (decoded_samples < 0) {
99+
return;
100+
}
101+
// Process the decoded PCM data
102+
AAudioStream_write(m_stream, pcm, decoded_samples, 0);
103+
}
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// Created by BangDC on 28/09/2024.
3+
//
4+
5+
#ifndef PIXELPILOT_AUDIODECODER_H
6+
#define PIXELPILOT_AUDIODECODER_H
7+
#include "libs/include/opus.h"
8+
#include <aaudio/AAudio.h>
9+
#include <memory>
10+
#include <queue>
11+
#include <mutex>
12+
#include <condition_variable>
13+
#include <thread>
14+
15+
typedef struct _AudioUDPPacket{
16+
_AudioUDPPacket(const uint8_t* _data, size_t _len)
17+
{
18+
memcpy(data, _data, _len);
19+
len = _len;
20+
};
21+
uint8_t data[250];
22+
size_t len;
23+
} AudioUDPPacket;
24+
25+
class AudioDecoder {
26+
public:
27+
AudioDecoder();
28+
~AudioDecoder();
29+
30+
// Audio buffer
31+
void initAudio();
32+
void enqueueAudio(const uint8_t *data, const std::size_t data_length);
33+
void startAudioProcessing() {
34+
stopAudioFlag = false;
35+
m_audioThread = std::thread(&AudioDecoder::processAudioQueue, this);
36+
}
37+
38+
void stopAudioProcessing() {
39+
{
40+
std::lock_guard<std::mutex> lock(m_mtxQueue);
41+
stopAudioFlag = true;
42+
}
43+
m_cvQueue.notify_all();
44+
if (m_audioThread.joinable()) {
45+
m_audioThread.join();
46+
}
47+
}
48+
void processAudioQueue();
49+
void stopAudio();
50+
bool isInit = false;
51+
52+
private:
53+
void onNewAudioData(const uint8_t *data, const std::size_t data_length);
54+
55+
private:
56+
const int BUFFER_CAPACITY_IN_FRAMES = (1024 + 256);
57+
std::queue<AudioUDPPacket> m_audioQueue;
58+
std::mutex m_mtxQueue;
59+
std::condition_variable m_cvQueue;
60+
bool stopAudioFlag = false;
61+
std::thread m_audioThread;
62+
AAudioStreamBuilder* m_builder = nullptr;
63+
AAudioStream* m_stream = nullptr;
64+
OpusDecoder *pOpusDecoder = nullptr;
65+
};
66+
#endif //PIXELPILOT_AUDIODECODER_H

app/videonative/src/main/cpp/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ cmake_minimum_required(VERSION 3.6)
33

44
project("VideoNative")
55

6+
include_directories(libs/include)
7+
68
add_library(${CMAKE_PROJECT_NAME} SHARED
79
parser/H26XParser.cpp
810
parser/ParseRTP.cpp
11+
AudioDecoder.cpp
912
UdpReceiver.cpp
1013
VideoDecoder.cpp
1114
VideoPlayer.cpp)
@@ -15,6 +18,8 @@ target_link_libraries(${CMAKE_PROJECT_NAME}
1518
# List libraries link to the target library
1619
android
1720
mediandk
21+
aaudio
22+
${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libopus.so
1823
log)
1924

2025
set_property(TARGET ${CMAKE_PROJECT_NAME} PROPERTY CXX_STANDARD 20)

app/videonative/src/main/cpp/UdpReceiver.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ void UDPReceiver::receiveFromUDPLoop() {
9494
//wrap into unique pointer to avoid running out of stack
9595
const auto buff = std::make_unique<std::array<uint8_t, UDP_PACKET_MAX_SIZE>>();
9696

97-
MLOGE << "Listening on " << INADDR_ANY << ":" << mPort;
97+
MLOGD << "Listening on " << INADDR_ANY << ":" << mPort;
9898

9999
sockaddr_in source;
100100
socklen_t sourceLen = sizeof(sockaddr_in);

app/videonative/src/main/cpp/VideoDecoder.cpp

+9-5
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ VideoDecoder::VideoDecoder(JNIEnv *env) {
2323

2424
void VideoDecoder::setOutputSurface(JNIEnv *env, jobject surface, jint idx) {
2525
if (surface == nullptr) {
26-
MLOGD << "Set output null surface";
26+
MLOGD << "Set output null surface idx: " << idx;
2727
//assert(decoder.window!=nullptr);
28-
if (decoder.window[idx] == nullptr || decoder.codec[idx] == nullptr) {
28+
if (decoder.window[idx] == nullptr && decoder.codec[idx] == nullptr) {
2929
//MLOGD<<"Decoder window is already null";
3030
return;
3131
}
@@ -35,18 +35,22 @@ void VideoDecoder::setOutputSurface(JNIEnv *env, jobject surface, jint idx) {
3535
AMediaCodec_stop(decoder.codec[idx]);
3636
AMediaCodec_delete(decoder.codec[idx]);
3737
decoder.codec[idx] = nullptr;
38+
MLOGD << "Set decoder.codec null idx: " << idx;
3839
mKeyFrameFinder.reset();
3940
decoder.configured[idx] = false;
4041
if (mCheckOutputThread[idx]->joinable()) {
4142
mCheckOutputThread[idx]->join();
4243
mCheckOutputThread[idx].reset();
4344
}
4445
}
45-
ANativeWindow_release(decoder.window[idx]);
46-
decoder.window[idx] = nullptr;
46+
if (decoder.window[idx]) {
47+
ANativeWindow_release(decoder.window[idx]);
48+
decoder.window[idx] = nullptr;
49+
MLOGD << "Set decoder.window null idx: " << idx;
50+
}
4751
resetStatistics();
4852
} else {
49-
MLOGD << "Set output non-null surface";
53+
MLOGD << "Set output non-null surface idx :" << idx;
5054
// Throw warning if the surface is set without clearing it first
5155
assert(decoder.window[idx] == nullptr);
5256
decoder.window[idx] = ANativeWindow_fromSurface(env, surface);

app/videonative/src/main/cpp/VideoPlayer.cpp

+29-4
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,15 @@ void VideoPlayer::processQueue() {
9393
}
9494

9595
//Not yet parsed bit stream (e.g. raw h264 or rtp data)
96-
void VideoPlayer::onNewVideoData(const uint8_t *data, const std::size_t data_length) {
97-
//MLOGD << "onNewVideoData " << data_length;
98-
mParser.parse_rtp_stream(data, data_length);
96+
void VideoPlayer::onNewRTPData(const uint8_t *data, const std::size_t data_length) {
97+
const RTP::RTPPacket rtpPacket(data, data_length);
98+
if (rtpPacket.header.payload == RTP_PAYLOAD_TYPE_AUDIO) {
99+
audioDecoder.enqueueAudio(data, data_length);
100+
}
101+
else
102+
{
103+
mParser.parse_rtp_stream(data, data_length);
104+
}
99105
}
100106

101107
void VideoPlayer::onNewNALU(const NALU &nalu) {
@@ -126,7 +132,7 @@ void VideoPlayer::start(JNIEnv *env, jobject androidContext) {
126132
mUDPReceiver = std::make_unique<UDPReceiver>(javaVm, VS_PORT, "UdpReceiver", -16,
127133
[this](const uint8_t *data,
128134
size_t data_length) {
129-
onNewVideoData(data, data_length);
135+
onNewRTPData(data, data_length);
130136
}, WANTED_UDP_RCVBUF_SIZE);
131137
mUDPReceiver->startReceiving();
132138
}
@@ -136,6 +142,8 @@ void VideoPlayer::stop(JNIEnv *env, jobject androidContext) {
136142
mUDPReceiver->stopReceiving();
137143
mUDPReceiver.reset();
138144
}
145+
146+
audioDecoder.stopAudio();
139147
}
140148

141149
std::string VideoPlayer::getInfoString() const {
@@ -315,3 +323,20 @@ Java_com_openipc_videonative_VideoPlayer_nativeIsRecording(JNIEnv *env, jclass c
315323
jlong native_instance) {
316324
return native(native_instance)->isRecording();
317325
}
326+
extern "C"
327+
JNIEXPORT void JNICALL
328+
Java_com_openipc_videonative_VideoPlayer_nativeStartAudio(JNIEnv *env, jclass clazz,
329+
jlong native_instance) {
330+
if(!native(native_instance)->audioDecoder.isInit)
331+
{
332+
native(native_instance)->audioDecoder.initAudio();
333+
}
334+
native(native_instance)->audioDecoder.stopAudioProcessing();
335+
native(native_instance)->audioDecoder.startAudioProcessing();
336+
}
337+
extern "C"
338+
JNIEXPORT void JNICALL
339+
Java_com_openipc_videonative_VideoPlayer_nativeStopAudio(JNIEnv *env, jclass clazz,
340+
jlong native_instance) {
341+
native(native_instance)->audioDecoder.stopAudioProcessing();
342+
}

app/videonative/src/main/cpp/VideoPlayer.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
#ifndef FPV_VR_VIDEOPLAYERN_H
66
#define FPV_VR_VIDEOPLAYERN_H
7-
87
#include <jni.h>
98
#include "VideoDecoder.h"
9+
#include "AudioDecoder.h"
1010
#include "UdpReceiver.h"
1111
#include "parser/H26XParser.h"
1212
#include "minimp4.h"
@@ -21,7 +21,7 @@ class VideoPlayer {
2121
public:
2222
VideoPlayer(JNIEnv *env, jobject context);
2323

24-
void onNewVideoData(const uint8_t *data, const std::size_t data_length);
24+
void onNewRTPData(const uint8_t *data, const std::size_t data_length);
2525

2626
/*
2727
* Set the surface the decoder can be configured with. When @param surface==nullptr
@@ -103,6 +103,7 @@ class VideoPlayer {
103103
void processQueue();
104104

105105
public:
106+
AudioDecoder audioDecoder;
106107
VideoDecoder videoDecoder;
107108
std::unique_ptr<UDPReceiver> mUDPReceiver;
108109
long nNALUsAtLastCall = 0;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)