Comments (11)
I think I found the problem: The mirroring thread doesn't properly handle read timeouts, so that it tries to receive a header when it got a timeout while receiving the payload. If the size of the payload data already received exceeded 128 bytes, the recv length parameter would underflow and cause invalid memory access. Please try if replacing the raop_rtp_mirror_thread function in 'lib/raop_rtp_mirroring.c' with this works:
static THREAD_RETVAL
raop_rtp_mirror_thread(void *arg)
{
raop_rtp_mirror_t *raop_rtp_mirror = arg;
assert(raop_rtp_mirror);
int stream_fd = -1;
unsigned char packet[128];
memset(packet, 0 , 128);
unsigned char* payload = NULL;
unsigned int readstart = 0;
#ifdef DUMP_H264
// C decrypted
FILE* file = fopen("/home/pi/Airplay.h264", "wb");
// Encrypted source file
FILE* file_source = fopen("/home/pi/Airplay.source", "wb");
FILE* file_len = fopen("/home/pi/Airplay.len", "wb");
#endif
while (1) {
fd_set rfds;
struct timeval tv;
int nfds, ret;
MUTEX_LOCK(raop_rtp_mirror->run_mutex);
if (!raop_rtp_mirror->running) {
MUTEX_UNLOCK(raop_rtp_mirror->run_mutex);
break;
}
MUTEX_UNLOCK(raop_rtp_mirror->run_mutex);
/* Set timeout value to 5ms */
tv.tv_sec = 0;
tv.tv_usec = 5000;
/* Get the correct nfds value and set rfds */
FD_ZERO(&rfds);
if (stream_fd == -1) {
FD_SET(raop_rtp_mirror->mirror_data_sock, &rfds);
nfds = raop_rtp_mirror->mirror_data_sock+1;
} else {
FD_SET(stream_fd, &rfds);
nfds = stream_fd+1;
}
ret = select(nfds, &rfds, NULL, NULL, &tv);
if (ret == 0) {
/* Timeout happened */
continue;
} else if (ret == -1) {
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in select");
break;
}
if (stream_fd == -1 && FD_ISSET(raop_rtp_mirror->mirror_data_sock, &rfds)) {
struct sockaddr_storage saddr;
socklen_t saddrlen;
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror accepting client");
saddrlen = sizeof(saddr);
stream_fd = accept(raop_rtp_mirror->mirror_data_sock, (struct sockaddr *)&saddr, &saddrlen);
if (stream_fd == -1) {
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in accept %d %s", errno, strerror(errno));
break;
}
// We're calling recv for a certain amount of data, so we need a timeout
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 5000;
if (setsockopt(stream_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror could not set stream socket timeout %d %s", errno, strerror(errno));
break;
}
}
if (stream_fd != -1 && FD_ISSET(stream_fd, &rfds)) {
// The first 128 bytes are some kind of header for the payload that follows
while (payload == NULL && readstart < 128) {
ret = recv(stream_fd, packet + readstart, 128 - readstart, 0);
if (ret <= 0) break;
readstart = readstart + ret;
}
if (payload == NULL && ret == 0) {
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror tcp socket closed");
break;
} else if (payload == NULL && ret == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) continue; // Timeouts can happen even if the connection is fine
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in header recv: %d", errno);
break;
}
int payload_size = byteutils_get_int(packet, 0);
unsigned short payload_type = byteutils_get_short(packet, 4) & 0xff;
unsigned short payload_option = byteutils_get_short(packet, 6);
if (payload == NULL) {
payload = malloc(payload_size);
readstart = 0;
}
while (readstart < payload_size) {
// Payload data
ret = recv(stream_fd, payload + readstart, payload_size - readstart, 0);
if (ret <= 0) break;
readstart = readstart + ret;
}
if (ret == 0) {
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror tcp socket closed");
break;
} else if (ret == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) continue; // Timeouts can happen even if the connection is fine
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in recv: %d", errno);
break;
}
if (payload_type == 0) {
// Normal video data (VCL NAL)
// Conveniently, the video data is already stamped with the remote wall clock time,
// so no additional clock syncing needed. The only thing odd here is that the video
// ntp time stamps don't include the SECONDS_FROM_1900_TO_1970, so it's really just
// counting micro seconds since last boot.
uint64_t ntp_timestamp_raw = byteutils_get_long(packet, 8);
uint64_t ntp_timestamp_remote = raop_ntp_timestamp_to_micro_seconds(ntp_timestamp_raw, false);
uint64_t ntp_timestamp = raop_ntp_convert_remote_time(raop_rtp_mirror->ntp, ntp_timestamp_remote);
uint64_t ntp_now = raop_ntp_get_local_time(raop_rtp_mirror->ntp);
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror video ntp = %llu, now = %llu, latency = %lld",
ntp_timestamp, ntp_now, ((int64_t) ntp_now) - ((int64_t) ntp_timestamp));
#ifdef DUMP_H264
fwrite(payload, payload_size, 1, file_source);
fwrite(&readstart, sizeof(readstart), 1, file_len);
#endif
// Decrypt data
unsigned char* payload_decrypted = malloc(payload_size);
mirror_buffer_decrypt(raop_rtp_mirror->buffer, payload, payload_decrypted, payload_size);
int nalu_type = payload[4] & 0x1f;
int nalu_size = 0;
int nalus_count = 0;
// It seems the AirPlay protocol prepends NALs with their size, which we're replacing with the 4-byte
// start code for the NAL Byte-Stream Format.
while (nalu_size < payload_size) {
int nc_len = (payload_decrypted[nalu_size + 0] << 24) | (payload_decrypted[nalu_size + 1] << 16) |
(payload_decrypted[nalu_size + 2] << 8) | (payload_decrypted[nalu_size + 3]);
assert(nc_len > 0);
payload_decrypted[nalu_size + 0] = 0;
payload_decrypted[nalu_size + 1] = 0;
payload_decrypted[nalu_size + 2] = 0;
payload_decrypted[nalu_size + 3] = 1;
nalu_size += nc_len + 4;
nalus_count++;
}
// logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "nalutype = %d", nalu_type);
// logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "nalu_size = %d, payloadsize = %d nalus_count = %d",
// nalu_size, payload_size, nalus_count);
#ifdef DUMP_H264
fwrite(payload_decrypted, payload_size, 1, file);
#endif
h264_decode_struct h264_data;
h264_data.data_len = payload_size;
h264_data.data = payload_decrypted;
h264_data.frame_type = 1;
h264_data.pts = ntp_timestamp;
raop_rtp_mirror->callbacks.video_process(raop_rtp_mirror->callbacks.cls, raop_rtp_mirror->ntp, &h264_data);
free(payload_decrypted);
} else if ((payload_type & 255) == 1) {
// The information in the payload contains an SPS and a PPS NAL
float width_source = byteutils_get_float(packet, 40);
float height_source = byteutils_get_float(packet, 44);
float width = byteutils_get_float(packet, 56);
float height = byteutils_get_float(packet, 60);
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror width_source = %f height_source = %f width = %f height = %f",
width_source, height_source, width, height);
// The sps_pps is not encrypted
h264codec_t h264;
h264.version = payload[0];
h264.profile_high = payload[1];
h264.compatibility = payload[2];
h264.level = payload[3];
h264.reserved_6_and_nal = payload[4];
h264.reserved_3_and_sps = payload[5];
h264.sps_size = (short) (((payload[6] & 255) << 8) + (payload[7] & 255));
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror sps size = %d", h264.sps_size);
h264.sequence_parameter_set = malloc(h264.sps_size);
memcpy(h264.sequence_parameter_set, payload + 8, h264.sps_size);
h264.number_of_pps = payload[h264.sps_size + 8];
h264.pps_size = (short) (((payload[h264.sps_size + 9] & 2040) + payload[h264.sps_size + 10]) & 255);
h264.picture_parameter_set = malloc(h264.pps_size);
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror pps size = %d", h264.pps_size);
memcpy(h264.picture_parameter_set, payload + h264.sps_size + 11, h264.pps_size);
if (h264.sps_size + h264.pps_size < 102400) {
// Copy the sps and pps into a buffer to hand to the decoder
int sps_pps_len = (h264.sps_size + h264.pps_size) + 8;
unsigned char sps_pps[sps_pps_len];
sps_pps[0] = 0;
sps_pps[1] = 0;
sps_pps[2] = 0;
sps_pps[3] = 1;
memcpy(sps_pps + 4, h264.sequence_parameter_set, h264.sps_size);
sps_pps[h264.sps_size + 4] = 0;
sps_pps[h264.sps_size + 5] = 0;
sps_pps[h264.sps_size + 6] = 0;
sps_pps[h264.sps_size + 7] = 1;
memcpy(sps_pps + h264.sps_size + 8, h264.picture_parameter_set, h264.pps_size);
#ifdef DUMP_H264
fwrite(sps_pps, sps_pps_len, 1, file);
#endif
h264_decode_struct h264_data;
h264_data.data_len = sps_pps_len;
h264_data.data = sps_pps;
h264_data.frame_type = 0;
h264_data.pts = 0;
raop_rtp_mirror->callbacks.video_process(raop_rtp_mirror->callbacks.cls, raop_rtp_mirror->ntp, &h264_data);
}
free(h264.picture_parameter_set);
free(h264.sequence_parameter_set);
}
free(payload);
payload = NULL;
memset(packet, 0, 128);
readstart = 0;
}
}
/* Close the stream file descriptor */
if (stream_fd != -1) {
closesocket(stream_fd);
}
#ifdef DUMP_H264
fclose(file);
fclose(file_source);
fclose(file_len);
#endif
// Ensure running reflects the actual state
MUTEX_LOCK(raop_rtp_mirror->run_mutex);
raop_rtp_mirror->running = false;
MUTEX_UNLOCK(raop_rtp_mirror->run_mutex);
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror exiting TCP thread");
return 0;
}
from rpiplay.
Does rebooting the iPhone fix the problem?
from rpiplay.
No,but when i reboot my iphone, it can be normal for a little while, and then stuck again.
from rpiplay.
Please use the debug log flag, reproduce the problem and paste the complete log here.
from rpiplay.
I want to confirm that your patch works well.
I had the same problem as described and made the changes as stated.
A video i took on my iPad now works fine - picture and sound are ok.
But - whenever i try to play locally stored music with the music app via Airplay to my TV i hear no sound and see the following error messages displayed:
aacDecoder_DecodeFrame error : 0x400c
aacDecoder_DecodeFrame error : 0x4002
Any idea how to fix this?
from rpiplay.
@Wonderwuzi2000 thanks for the confirmation! For the aac decoder problem, please open a separate issue with as many details as you can give (iOS version, complete debug log etc).
from rpiplay.
Yes, it works. Thank you.
By the way, is there some project support Android devices?
from rpiplay.
RPiPlay is specifically and exclusively for the Raspberry Pi. If you want a similar Android project, have a look at @dsafa22's AirplayServer project (which RPiPlay is derived from).
from rpiplay.
RPiPlay is specifically and exclusively for the Raspberry Pi. If you want a similar Android project, have a look at @dsafa22's AirplayServer project (which RPiPlay is derived from).
Sorry for my poor english...
I want to know is there some project can be run on Raspberry Pi like your project, and support android phone mirror ? not run on Android device.
from rpiplay.
I see. There are two possibilities:
-
Write an Android app that uses the MediaProjection API to capture the screen content and implement the client side of the AirPlay protocol to send it to RPiPlay. This would have the disadvantage that DRM-protected content and apps that declare their content sensitive are not available through the MediaProjection API. There are a few such apps on the Play Store, though I don't know whether they are compatible with RPiPlay (I tested one and it used the ancient pre-iOS 9 AirPlay mirroring protocol, which is not supported by RPiPlay)
-
Write a Chromecast receiver program for the Raspberry Pi. This is definitely not a trivial task, as it seems the protocol is even less documented than AirPlay, so you'd have to reverse-engineer it all on your own.
None of these is in the scope of the RPiPlay project.
from rpiplay.
Android mirroring (and control) on the pi works really well with this tool: https://github.com/Genymobile/scrcpy
from rpiplay.
Related Issues (20)
- ^hello
- RPi Play still showing in Screen Mirroring
- Raspberry PI Raspi 4 no Airplay output on HDMI ports HOT 5
- Nothing happens when trying to screenshare HOT 10
- no audio using screen mirroring
- How do you uninstall? HOT 1
- Is there Raspi Image HOT 1
- Screen is tearing with gstreamer HOT 2
- make -j or j1 error HOT 1
- Display of Apple device not appearing HOT 3
- RPiPlay doesn't support Raspberry Pi OS Bullseye HOT 1
- RPiPlay on Raspberry Pi 4 B 8B Issue HOT 7
- How to get the screen resolution of iOS device?
- Building Error during Make HOT 1
- use RPiPlay on analog output
- Unable to stream on screen error ** (rpiplay:7237): CRITICAL **: 21:32:04.090: gst_app_src_push_internal: assertion 'GST_IS_APP_SRC (appsrc)' failed HOT 2
- Stop Replying To HDCP Requests
- Not showing on Raspberry pi 5 32Bits HOT 2
- Impossible to Screen mirror Netflix HOT 6
- BUG: Video start delaying after iphone display rotated to horizontal HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rpiplay.