Git Product home page Git Product logo

Comments (11)

FD- avatar FD- commented on August 16, 2024 1

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.

FD- avatar FD- commented on August 16, 2024

Does rebooting the iPhone fix the problem?

from rpiplay.

joelihn avatar joelihn commented on August 16, 2024

No,but when i reboot my iphone, it can be normal for a little while, and then stuck again.

from rpiplay.

FD- avatar FD- commented on August 16, 2024

Please use the debug log flag, reproduce the problem and paste the complete log here.

from rpiplay.

Wonderwuzi2000 avatar Wonderwuzi2000 commented on August 16, 2024

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.

FD- avatar FD- commented on August 16, 2024

@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.

joelihn avatar joelihn commented on August 16, 2024

Yes, it works. Thank you.

By the way, is there some project support Android devices?

from rpiplay.

FD- avatar FD- commented on August 16, 2024

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.

joelihn avatar joelihn commented on August 16, 2024

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.

FD- avatar FD- commented on August 16, 2024

I see. There are two possibilities:

  1. 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)

  2. 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.

svarvel avatar svarvel commented on August 16, 2024

Android mirroring (and control) on the pi works really well with this tool: https://github.com/Genymobile/scrcpy

from rpiplay.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.