Ticket #10310: RAOP_http_digest.2.patch
File RAOP_http_digest.2.patch, 31.9 KB (added by , 12 years ago) |
---|
-
mythtv/libs/libmythtv/mythraopconnection.cpp
diff --git a/mythtv/libs/libmythtv/mythraopconnection.cpp b/mythtv/libs/libmythtv/mythraopconnection.cpp index 66b97c7..5ad43b7 100644
a b 6 6 #include <QUdpSocket> 7 7 #include <QtEndian> 8 8 9 #include <QFile> 10 #include <QCryptographicHash> 9 11 #include "mythlogging.h" 10 12 #include "mythcorecontext.h" 11 13 … … 14 16 15 17 #include "mythraopdevice.h" 16 18 #include "mythraopconnection.h" 19 #include "mythdirs.h" 17 20 18 21 #define LOC QString("RAOP Conn: ") 19 22 #define MAX_PACKET_SIZE 2048 20 23 #define DEFAULT_SAMPLE_RATE 44100 21 24 22 RSA *MythRAOPConnection::g_rsa = NULL;25 RSA *MythRAOPConnection::g_rsa = NULL; 23 26 24 27 MythRAOPConnection::MythRAOPConnection(QTcpSocket *socket, QByteArray id, int port) 25 : m_watchdogTimer(NULL), m_socket(socket), m_hardwareId(id), m_dataPort(port), 26 m_dataSocket(NULL), m_clientControlSocket(NULL), m_clientControlPort(0), 27 m_audio(NULL), m_codec(NULL), m_codeccontext(NULL), 28 m_sampleRate(DEFAULT_SAMPLE_RATE), m_allowVolumeControl(true), 29 m_seenPacket(false), m_lastPacketSequence(0), m_lastPacketTimestamp(0), 30 m_lastSyncTime(0), m_lastSyncTimestamp(0), m_lastLatency(0), m_latencyAudio(0), 31 m_latencyQueued(0), m_latencyCounter(0), m_avSync(0) 28 : m_watchdogTimer(NULL), m_socket(socket), m_hardwareId(id), m_dataPort(port), 29 m_dataSocket(NULL), m_clientControlSocket(NULL), m_clientControlPort(0), 30 m_audio(NULL), m_codec(NULL), m_codeccontext(NULL), 31 m_sampleRate(DEFAULT_SAMPLE_RATE), m_allowVolumeControl(true), 32 m_seenPacket(false), m_authed(false), m_nonce(""), 33 m_lastPacketSequence(0), m_lastPacketTimestamp(0), 34 m_lastSyncTime(0), m_lastSyncTimestamp(0), m_lastLatency(0), m_latencyAudio(0), 35 m_latencyQueued(0), m_latencyCounter(0), m_avSync(0) 32 36 { 33 37 } 34 38 … … MythRAOPConnection::~MythRAOPConnection() 37 41 // stop and delete watchdog timer 38 42 if (m_watchdogTimer) 39 43 m_watchdogTimer->stop(); 44 40 45 delete m_watchdogTimer; 41 46 m_watchdogTimer = NULL; 42 47 … … MythRAOPConnection::~MythRAOPConnection() 46 51 m_socket->close(); 47 52 m_socket->deleteLater(); 48 53 } 54 49 55 m_socket = NULL; 50 56 51 57 // delete data socket … … MythRAOPConnection::~MythRAOPConnection() 55 61 m_dataSocket->close(); 56 62 m_dataSocket->deleteLater(); 57 63 } 64 58 65 m_dataSocket = NULL; 59 66 60 67 // client control socket … … MythRAOPConnection::~MythRAOPConnection() 64 71 m_clientControlSocket->close(); 65 72 m_clientControlSocket->deleteLater(); 66 73 } 74 67 75 m_clientControlSocket = NULL; 68 76 69 77 // close audio decoder … … bool MythRAOPConnection::Init(void) 81 89 // connect up the request socket 82 90 m_textStream = new QTextStream(m_socket); 83 91 m_textStream->setCodec("UTF-8"); 92 84 93 if (!connect(m_socket, SIGNAL(readyRead()), this, SLOT(readClient()))) 85 94 { 86 95 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to connect client socket signal."); … … bool MythRAOPConnection::Init(void) 89 98 90 99 // create the data socket 91 100 m_dataSocket = new QUdpSocket(); 101 92 102 if (!connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(udpDataReady()))) 93 103 { 94 104 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to connect data socket signal."); … … bool MythRAOPConnection::Init(void) 97 107 98 108 // try a few ports in case the first is in use 99 109 int baseport = m_dataPort; 110 100 111 while (m_dataPort < baseport + RAOP_PORT_RANGE) 101 112 { 102 113 if (m_dataSocket->bind(gCoreContext->MythHostAddressAny(), m_dataPort)) … … bool MythRAOPConnection::Init(void) 105 116 QString("Bound to port %1 for incoming data").arg(m_dataPort)); 106 117 break; 107 118 } 119 108 120 m_dataPort++; 109 121 } 110 122 … … bool MythRAOPConnection::Init(void) 131 143 132 144 void MythRAOPConnection::udpDataReady() 133 145 { 134 QUdpSocket *sender = (QUdpSocket*)this->sender(); 146 QUdpSocket *sender = (QUdpSocket *)this->sender(); 147 135 148 if (!sender) 136 149 return; 137 150 … … void MythRAOPConnection::udpDataReady() 146 159 147 160 // read datagrams 148 161 QByteArray buf; 162 149 163 while (sender->state() == QAbstractSocket::BoundState && 150 164 sender->hasPendingDatagrams()) 151 165 { … … void MythRAOPConnection::udpDataReady() 153 167 QHostAddress peer; 154 168 quint16 port; 155 169 156 sender->readDatagram(buf.data(), buf.size(), &peer, &port);170 sender->readDatagram(buf.data(), buf.size(), &peer, &port); 157 171 158 172 if (!m_audio || !m_codec || !m_codeccontext) 159 173 continue; … … void MythRAOPConnection::udpDataReady() 168 182 169 183 int offset = type == 0x60 ? 0 : 4; 170 184 uint16_t this_sequence = ntohs(*(uint16_t *)(buf.data() + offset + 2)); 171 uint64_t this_timestamp = FramesToMs(ntohl(*(uint64_t *)(buf.data() + offset + 4)));185 uint64_t this_timestamp = FramesToMs(ntohl(*(uint64_t *)(buf.data() + offset + 4))); 172 186 uint16_t expected_sequence = m_lastPacketSequence + 1; // should wrap ok 173 187 174 188 // regular data packet … … void MythRAOPConnection::udpDataReady() 199 213 ExpireResendRequests(timenow); 200 214 201 215 offset += 12; 202 char *data_in = buf.data() + offset;216 char *data_in = buf.data() + offset; 203 217 int len = buf.size() - offset; 218 204 219 if (len < 16) 205 220 continue; 206 221 … … void MythRAOPConnection::udpDataReady() 208 223 unsigned char iv[16]; 209 224 unsigned char decrypted_data[MAX_PACKET_SIZE]; 210 225 memcpy(iv, m_AESIV.data(), sizeof(iv)); 211 AES_cbc_encrypt((const unsigned char *)data_in,226 AES_cbc_encrypt((const unsigned char *)data_in, 212 227 decrypted_data, aeslen, 213 228 &m_aesKey, iv, AES_DECRYPT); 214 229 memcpy(decrypted_data + aeslen, data_in + aeslen, len - aeslen); … … void MythRAOPConnection::udpDataReady() 218 233 tmp_pkt.data = decrypted_data; 219 234 tmp_pkt.size = len; 220 235 int decoded_data_size = AVCODEC_MAX_AUDIO_FRAME_SIZE; 221 int16_t *samples = (int16_t *)av_mallocz(AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof(int32_t));236 int16_t *samples = (int16_t *)av_mallocz(AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof(int32_t)); 222 237 int used = avcodec_decode_audio3(m_codeccontext, samples, 223 238 &decoded_data_size, &tmp_pkt); 239 224 240 if (used != len) 225 241 { 226 242 LOG(VB_GENERAL, LOG_WARNING, LOC + … … void MythRAOPConnection::SendResendRequest(uint64_t timenow, 256 272 return; 257 273 258 274 int16_t missed = (got < expected) ? 259 (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :260 got - expected;275 (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) : 276 got - expected; 261 277 262 278 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Missed %1 packet(s): expected %2 got %3") 263 279 .arg(missed).arg(expected).arg(got)); … … void MythRAOPConnection::ExpireResendRequests(uint64_t timenow) 288 304 return; 289 305 290 306 uint64_t too_old = timenow - 250; 291 QMutableMapIterator<uint16_t,uint64_t> it(m_resends); 307 QMutableMapIterator<uint16_t, uint64_t> it(m_resends); 308 292 309 while (it.hasNext()) 293 310 { 294 311 it.next(); 312 295 313 if (it.value() < too_old) 296 314 { 297 315 LOG(VB_GENERAL, LOG_WARNING, LOC + … … void MythRAOPConnection::ExpireResendRequests(uint64_t timenow) 303 321 304 322 void MythRAOPConnection::ProcessSyncPacket(const QByteArray &buf, uint64_t timenow) 305 323 { 306 m_lastSyncTimestamp = FramesToMs(ntohl(*(uint64_t *)(buf.data() + 4)));307 uint64_t now = FramesToMs(ntohl(*(uint64_t *)(buf.data() + 16)));324 m_lastSyncTimestamp = FramesToMs(ntohl(*(uint64_t *)(buf.data() + 4))); 325 uint64_t now = FramesToMs(ntohl(*(uint64_t *)(buf.data() + 16))); 308 326 m_lastLatency = now - m_lastSyncTimestamp; 309 327 m_lastSyncTime = timenow; 310 328 uint64_t averageaudio = 0; 311 329 uint64_t averagequeue = 0; 312 330 double averageav = 0; 331 313 332 if (m_latencyCounter) 314 333 { 315 334 averageaudio = m_latencyAudio / m_latencyCounter; … … void MythRAOPConnection::ProcessSyncPacket(const QByteArray &buf, uint64_t timen 326 345 .arg(averageaudio).arg(averagequeue) 327 346 .arg(averageaudio + averagequeue).arg(m_lastLatency)); 328 347 } 348 329 349 m_latencyAudio = m_latencyQueued = m_latencyCounter = m_avSync = 0; 330 350 } 331 351 332 352 int MythRAOPConnection::ExpireAudio(uint64_t timestamp) 333 353 { 334 354 int res = 0; 335 QMutableMapIterator<uint64_t,int16_t*> it(m_audioQueue); 355 QMutableMapIterator<uint64_t, int16_t *> it(m_audioQueue); 356 336 357 while (it.hasNext()) 337 358 { 338 359 it.next(); 360 339 361 if (it.key() < timestamp) 340 362 { 341 av_freep((void *) &(it.value()));363 av_freep((void *) & (it.value())); 342 364 m_audioQueue.remove(it.key()); 343 365 res++; 344 366 } 345 367 } 368 346 369 return res; 347 370 } 348 371 … … void MythRAOPConnection::ProcessAudio(uint64_t timenow) 358 381 359 382 // expire anything that is late 360 383 int dumped = ExpireAudio(updatedsync); 384 361 385 if (dumped > 0) 362 386 { 363 387 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Dumped %1 audio packets") … … void MythRAOPConnection::ProcessAudio(uint64_t timenow) 373 397 m_latencyQueued += queue_length; 374 398 m_latencyCounter++; 375 399 376 QMapIterator<uint64_t,int16_t*> it(m_audioQueue); 400 QMapIterator<uint64_t, int16_t *> it(m_audioQueue); 401 377 402 while (it.hasNext()) 378 403 { 379 404 it.next(); 405 380 406 if (it.key() < ideal_ts) 381 407 m_audio->AddData(it.value(), 1408 /* FIXME */, it.key(), 352); 382 408 } … … void MythRAOPConnection::ResetAudio(void) 389 415 ExpireAudio(UINT64_MAX); 390 416 m_latencyCounter = m_latencyAudio = m_latencyQueued = m_avSync = 0; 391 417 m_seenPacket = false; 418 392 419 if (m_audio) 393 420 m_audio->Reset(); 394 421 } … … void MythRAOPConnection::timeout(void) 402 429 void MythRAOPConnection::readClient(void) 403 430 { 404 431 QTcpSocket *socket = (QTcpSocket *)sender(); 432 405 433 if (!socket) 406 434 return; 407 435 408 436 QList<QByteArray> lines; 437 409 438 while (socket->canReadLine()) 410 439 { 411 440 QByteArray line = socket->readLine(); … … void MythRAOPConnection::readClient(void) 417 446 ProcessRequest(lines); 418 447 } 419 448 449 450 QString MythRAOPConnection::GenerateNonce(void) 451 { 452 int nonceParts[4]; 453 QString nonce; 454 QTime time = QTime::currentTime(); 455 qsrand((uint)time.msec()); 456 nonceParts[0] = qrand(); 457 nonceParts[1] = qrand(); 458 nonceParts[2] = qrand(); 459 nonceParts[3] = qrand(); 460 461 462 nonce = QString::number(nonceParts[0], 16).toUpper(); 463 nonce += QString::number(nonceParts[1], 16).toUpper(); 464 nonce += QString::number(nonceParts[2], 16).toUpper(); 465 nonce += QString::number(nonceParts[3], 16).toUpper(); 466 return nonce; 467 } 420 468 void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 421 469 { 422 470 if (lines.isEmpty()) … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 432 480 433 481 QByteArray option = lines[0].left(lines[0].indexOf(" ")); 434 482 483 bool passwordAuthEnabled = gCoreContext->GetNumSettingOnHost( 484 "RAOPPasswordEnabled", 485 gCoreContext->GetHostName(), 486 0); 487 488 if (passwordAuthEnabled && !m_authed) 489 { 490 491 if (!tags.contains("Authorization")) 492 { 493 // 60 seconds to enter password. 494 m_watchdogTimer->start(60000); 495 496 if (option != "OPTIONS" && option != "ANNOUNCE" 497 && option != "TEARDOWN") 498 { 499 StartResponse(m_textStream); 500 FinishResponse(m_textStream, m_socket, option, tags["Cseq"]); 501 return; 502 } 503 } 504 else 505 { 506 507 if (m_nonce.isEmpty()) 508 { 509 m_nonce = GenerateNonce(); 510 } 511 512 QByteArray response = tags["Authorization"].data(); 513 int authStart = response.indexOf("response=\"") + 10; 514 QByteArray auth = response.mid(authStart, 32); 515 516 int uriStart = response.indexOf("uri=\"") + 5; 517 int uriLength = response.indexOf("\"", uriStart) - uriStart; 518 QByteArray uri = response.mid(uriStart, uriLength); 519 520 QString password = gCoreContext->GetSettingOnHost("RAOPPassword", 521 gCoreContext->GetHostName(), 522 ""); 523 524 QString ha1input = "iTunes:raop:" + password; 525 QString ha2input = option + ":" + uri; 526 QString HA1, HA2, final; 527 QByteArray hashResult; 528 529 m_watchdogTimer->start(10000); 530 531 hashResult = QCryptographicHash::hash(ha1input.toAscii(), 532 QCryptographicHash::Md5); 533 HA1 = QString(hashResult.toHex()).toUpper(); 534 535 hashResult = QCryptographicHash::hash(ha2input.toAscii(), 536 QCryptographicHash::Md5); 537 HA2 = QString(hashResult.toHex()).toUpper(); 538 539 final = HA1 + ":" + m_nonce + ":" + HA2; 540 hashResult = QCryptographicHash::hash(final.toAscii(), 541 QCryptographicHash::Md5); 542 final = QString(hashResult.toHex()).toUpper(); 543 544 if (final.toAscii().toHex() == auth.toHex()) 545 { 546 m_authed = true; 547 LOG(VB_GENERAL, LOG_INFO, LOC + "RAOP client Authed"); 548 } 549 else 550 { 551 if (option != "OPTIONS" && option != "ANNOUNCE" 552 && option != "TEARDOWN") 553 { 554 StartResponse(m_textStream); 555 FinishResponse(m_textStream, m_socket, option, tags["Cseq"]); 556 return; 557 } 558 } 559 } 560 561 } 562 435 563 if (option == "OPTIONS") 436 564 { 437 565 StartResponse(m_textStream); 566 438 567 if (tags.contains("Apple-Challenge")) 439 568 { 440 569 *m_textStream << "Apple-Response: "; 570 441 571 if (!LoadKey()) 442 572 return; 573 443 574 int tosize = RSA_size(LoadKey()); 444 575 unsigned char to[tosize]; 445 576 446 577 QByteArray challenge = QByteArray::fromBase64(tags["Apple-Challenge"].data()); 447 578 int challenge_size = challenge.size(); 579 448 580 if (challenge_size != 16) 449 581 { 450 582 LOG(VB_GENERAL, LOG_ERR, LOC + 451 583 QString("Decoded challenge size %1, expected 16").arg(challenge_size)); 584 452 585 if (challenge_size > 16) 453 586 challenge_size = 16; 454 587 } … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 457 590 unsigned char from[38]; 458 591 memcpy(from, challenge.data(), challenge_size); 459 592 i += challenge_size; 593 460 594 if (m_socket->localAddress().protocol() == QAbstractSocket::IPv4Protocol) 461 595 { 462 596 uint32_t ip = m_socket->localAddress().toIPv4Address(); … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 472 606 memcpy(from + i, &ip, 16); 473 607 i += 16; 474 608 } 609 475 610 memcpy(from + i, m_hardwareId.data(), RAOP_HARDWARE_ID_SIZE); 476 611 i += RAOP_HARDWARE_ID_SIZE; 477 612 478 613 int pad = 32 - i; 614 479 615 if (pad > 0) 480 616 { 481 617 memset(from + i, 0, pad); … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 483 619 } 484 620 485 621 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Full base64 response: '%1' size %2") 486 .arg(QByteArray((const char *)from, i).toBase64().data()).arg(i));622 .arg(QByteArray((const char *)from, i).toBase64().data()).arg(i)); 487 623 488 624 RSA_private_encrypt(i, from, to, LoadKey(), RSA_PKCS1_PADDING); 489 625 490 QByteArray base64 = QByteArray((const char *)to, tosize).toBase64();626 QByteArray base64 = QByteArray((const char *)to, tosize).toBase64(); 491 627 492 628 for (int pos = base64.size() - 1; pos > 0; pos--) 493 629 { … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 505 641 } 506 642 else if (option == "ANNOUNCE") 507 643 { 508 foreach 644 foreach(QByteArray line, lines) 509 645 { 510 646 if (line.startsWith("a=rsaaeskey:")) 511 647 { … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 518 654 { 519 655 int size = sizeof(char) * RSA_size(LoadKey()); 520 656 char *decryptedkey = new char[size]; 657 521 658 if (RSA_private_decrypt(decodedkey.size(), 522 (const unsigned char *)decodedkey.data(),523 (unsigned char *)decryptedkey,659 (const unsigned char *)decodedkey.data(), 660 (unsigned char *)decryptedkey, 524 661 LoadKey(), RSA_PKCS1_OAEP_PADDING)) 525 662 { 526 663 LOG(VB_GENERAL, LOG_DEBUG, LOC + 527 664 "Successfully decrypted AES key from RSA."); 528 AES_set_decrypt_key((const unsigned char *)decryptedkey,665 AES_set_decrypt_key((const unsigned char *)decryptedkey, 529 666 128, &m_aesKey); 530 667 } 531 668 else … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 533 670 LOG(VB_GENERAL, LOG_WARNING, LOC + 534 671 "Failed to decrypt AES key from RSA."); 535 672 } 673 536 674 delete [] decryptedkey; 537 675 } 538 676 … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 549 687 m_audioFormat.clear(); 550 688 QByteArray format = line.mid(7).trimmed(); 551 689 QList<QByteArray> fmts = format.split(' '); 552 foreach 553 690 foreach(QByteArray fmt, fmts) 691 m_audioFormat.append(fmt.toInt()); 554 692 555 foreach 556 693 foreach(int fmt, m_audioFormat) 694 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Audio parameter: %1").arg(fmt)); 557 695 } 558 696 } 559 697 … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 568 706 int timing_port = 0; 569 707 QString data = tags["Transport"]; 570 708 QStringList items = data.split(";"); 571 foreach 709 foreach(QString item, items) 572 710 { 573 711 if (item.startsWith("control_port")) 574 712 control_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt(); … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 578 716 579 717 LOG(VB_GENERAL, LOG_INFO, LOC + 580 718 QString("Negotiated setup with client %1 on port %2") 581 582 719 .arg(m_socket->peerAddress().toString()) 720 .arg(m_socket->peerPort())); 583 721 LOG(VB_GENERAL, LOG_DEBUG, LOC + 584 722 QString("control port: %1 timing port: %2") 585 723 .arg(control_port).arg(timing_port)); 586 724 587 725 StartResponse(m_textStream); 588 726 *m_textStream << "Transport: " << tags["Transport"]; … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 600 738 } 601 739 602 740 m_clientControlSocket = new QUdpSocket(this); 741 603 742 if (!m_clientControlSocket->bind(control_port)) 604 743 { 605 744 LOG(VB_GENERAL, LOG_ERR, LOC + … … void MythRAOPConnection::ProcessRequest(const QList<QByteArray> &lines) 627 766 } 628 767 else if (option == "SET_PARAMETER") 629 768 { 630 foreach 769 foreach(QByteArray line, lines) 631 770 { 632 771 if (line.startsWith("volume:") && m_allowVolumeControl && m_audio) 633 772 { 634 773 QByteArray rawvol = line.mid(7).trimmed(); 635 774 float volume = (rawvol.toFloat() + 30.0) / 0.3; 775 636 776 if (volume < 0.01) 637 777 volume = 0.0; 778 638 779 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Setting volume to %1 (raw %3)") 639 780 .arg(volume).arg(rawvol.data())); 640 781 m_audio->SetCurrentVolume((int)volume); … … void MythRAOPConnection::StartResponse(QTextStream *stream) 663 804 { 664 805 if (!stream) 665 806 return; 807 808 if (gCoreContext->GetNumSettingOnHost("RAOPPasswordEnabled", 809 gCoreContext->GetHostName(), 0)) 810 { 811 if (!m_authed) 812 { 813 814 if (m_nonce.isEmpty()) 815 { 816 m_nonce = GenerateNonce(); 817 } 818 819 *stream << "RTSP/1.0 401 Unauthorised\r\n"; 820 *stream << "WWW-Authenticate: Digest realm=\"raop\","; 821 *stream << " nonce=\"" + m_nonce + "\"\r\n"; 822 return; 823 } 824 } 825 666 826 *stream << "RTSP/1.0 200 OK\r\n"; 667 827 } 668 828 … … RSA* MythRAOPConnection::LoadKey(void) 730 874 RawHash MythRAOPConnection::FindTags(const QList<QByteArray> &lines) 731 875 { 732 876 RawHash result; 877 733 878 if (lines.isEmpty()) 734 879 return result; 735 880 736 881 for (int i = 0; i < lines.size(); i++) 737 882 { 738 883 int index = lines[i].indexOf(":"); 884 739 885 if (index > 0) 740 886 { 741 887 result.insert(lines[i].left(index).trimmed(), 742 888 lines[i].mid(index + 1).trimmed()); 743 889 } 744 890 } 891 745 892 return result; 746 893 } 747 894 … … bool MythRAOPConnection::CreateDecoder(void) 755 902 avcodeclock->unlock(); 756 903 757 904 m_codec = avcodec_find_decoder(CODEC_ID_ALAC); 905 758 906 if (!m_codec) 759 907 { 760 908 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create ALAC decoder- going silent..."); … … bool MythRAOPConnection::CreateDecoder(void) 762 910 } 763 911 764 912 m_codeccontext = avcodec_alloc_context(); 913 765 914 if (m_codec && m_codeccontext) 766 915 { 767 unsigned char *extradata = new unsigned char[36];916 unsigned char *extradata = new unsigned char[36]; 768 917 memset(extradata, 0, 36); 918 769 919 if (m_audioFormat.size() < 12) 770 920 { 771 921 LOG(VB_GENERAL, LOG_ERR, LOC + "Creating decoder but haven't seen audio format."); … … bool MythRAOPConnection::CreateDecoder(void) 783 933 extradata[19] = m_audioFormat[5]; // rice_initialhistory 784 934 extradata[20] = m_audioFormat[6]; // rice_kmodifier 785 935 } 936 786 937 m_codeccontext->extradata = extradata; 787 938 m_codeccontext->extradata_size = 36; 788 939 m_codeccontext->channels = 2; 940 789 941 if (avcodec_open(m_codeccontext, m_codec) < 0) 790 942 { 791 943 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to open ALAC decoder - going silent..."); 792 944 DestroyDecoder(); 793 945 return false; 794 946 } 947 795 948 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Opened ALAC decoder."); 796 949 } 797 950 … … void MythRAOPConnection::DestroyDecoder(void) 805 958 avcodec_close(m_codeccontext); 806 959 av_free(m_codeccontext); 807 960 } 961 808 962 m_codec = NULL; 809 963 m_codeccontext = NULL; 810 964 } … … bool MythRAOPConnection::OpenAudioDevice(void) 814 968 CloseAudioDevice(); 815 969 816 970 m_sampleRate = m_audioFormat.size() >= 12 ? m_audioFormat[11] : DEFAULT_SAMPLE_RATE; 971 817 972 if (m_sampleRate < 1) 818 973 m_sampleRate = DEFAULT_SAMPLE_RATE; 819 974 820 975 QString passthru = gCoreContext->GetNumSetting("PassThruDeviceOverride", false) 821 976 ? gCoreContext->GetSetting("PassThruOutputDevice") : QString::null; 822 977 QString device = gCoreContext->GetSetting("AudioOutputDevice"); 823 978 824 979 m_audio = AudioOutput::OpenAudio(device, passthru, FORMAT_S16, 2, 825 980 0, m_sampleRate, AUDIOOUTPUT_MUSIC, 826 981 m_allowVolumeControl, false); 982 827 983 if (!m_audio) 828 984 { 829 985 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to open audio device. Going silent..."); -
mythtv/libs/libmythtv/mythraopconnection.h
diff --git a/mythtv/libs/libmythtv/mythraopconnection.h b/mythtv/libs/libmythtv/mythraopconnection.h index 8e34849..0c37047 100644
a b class MythRAOPConnection : public QObject 47 47 int ExpireAudio(uint64_t timestamp); 48 48 void ProcessAudio(uint64_t timenow); 49 49 void ResetAudio(void); 50 QString GenerateNonce(void); 50 51 void ProcessRequest(const QList<QByteArray> &lines); 51 52 void StartResponse(QTextStream *stream); 52 53 void FinishResponse(QTextStream *stream, QTcpSocket *socket, … … class MythRAOPConnection : public QObject 59 60 void CloseAudioDevice(void); 60 61 61 62 QTimer *m_watchdogTimer; 63 62 64 // comms socket 63 65 QTcpSocket *m_socket; 64 66 QTextStream *m_textStream; … … class MythRAOPConnection : public QObject 85 87 bool m_allowVolumeControl; 86 88 // audio/packet sync 87 89 bool m_seenPacket; 90 bool m_authed; 91 QString m_nonce; 88 92 int16_t m_lastPacketSequence; 89 93 uint64_t m_lastPacketTimestamp; 90 94 uint64_t m_lastSyncTime; -
mythtv/libs/libmythtv/mythraopdevice.cpp
diff --git a/mythtv/libs/libmythtv/mythraopdevice.cpp b/mythtv/libs/libmythtv/mythraopdevice.cpp index 8bb5f6f..69212d4 100644
a b 10 10 #include "mythraopconnection.h" 11 11 #include "mythraopdevice.h" 12 12 13 MythRAOPDevice *MythRAOPDevice::gMythRAOPDevice = NULL;14 MThread *MythRAOPDevice::gMythRAOPDeviceThread = NULL;15 QMutex *MythRAOPDevice::gMythRAOPDeviceMutex = new QMutex(QMutex::Recursive);13 MythRAOPDevice *MythRAOPDevice::gMythRAOPDevice = NULL; 14 MThread *MythRAOPDevice::gMythRAOPDeviceThread = NULL; 15 QMutex *MythRAOPDevice::gMythRAOPDeviceMutex = new QMutex(QMutex::Recursive); 16 16 17 17 #define LOC QString("RAOP Device: ") 18 18 … … bool MythRAOPDevice::Create(void) 23 23 // create the device thread 24 24 if (!gMythRAOPDeviceThread) 25 25 gMythRAOPDeviceThread = new MThread("RAOPDevice"); 26 26 27 if (!gMythRAOPDeviceThread) 27 28 { 28 29 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create RAOP device thread."); … … bool MythRAOPDevice::Create(void) 32 33 // create the device object 33 34 if (!gMythRAOPDevice) 34 35 gMythRAOPDevice = new MythRAOPDevice(); 36 35 37 if (!gMythRAOPDevice) 36 38 { 37 39 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create RAOP device object."); … … void MythRAOPDevice::Cleanup(void) 60 62 gMythRAOPDevice->Teardown(); 61 63 62 64 QMutexLocker locker(gMythRAOPDeviceMutex); 65 63 66 if (gMythRAOPDeviceThread) 64 67 { 65 68 gMythRAOPDeviceThread->exit(); 66 69 gMythRAOPDeviceThread->wait(); 67 70 } 71 68 72 delete gMythRAOPDeviceThread; 69 73 gMythRAOPDeviceThread = NULL; 70 74 … … void MythRAOPDevice::Cleanup(void) 73 77 } 74 78 75 79 MythRAOPDevice::MythRAOPDevice() 76 : QTcpServer(), m_name(QString("MythTV")), m_bonjour(NULL), m_valid(false),77 m_lock(new QMutex(QMutex::Recursive)), m_setupPort(5000)80 : QTcpServer(), m_name(QString("MythTV")), m_bonjour(NULL), m_valid(false), 81 m_lock(new QMutex(QMutex::Recursive)), m_setupPort(5000) 78 82 { 79 83 for (int i = 0; i < RAOP_HARDWARE_ID_SIZE; i++) 80 84 m_hardwareId.append((random() % 80) + 33); … … void MythRAOPDevice::Teardown(void) 100 104 m_bonjour = NULL; 101 105 102 106 // disconnect clients 103 foreach (MythRAOPConnection* client, m_clients)107 foreach(MythRAOPConnection * client, m_clients) 104 108 { 105 109 disconnect(client->GetSocket(), 0, 0, 0); 106 110 delete client; … … void MythRAOPDevice::Start(void) 121 125 122 126 // start listening for connections (try a few ports in case the default is in use) 123 127 int baseport = m_setupPort; 128 124 129 while (m_setupPort < baseport + RAOP_PORT_RANGE) 125 130 { 126 131 if (listen(QHostAddress::Any, m_setupPort)) … … void MythRAOPDevice::Start(void) 129 134 QString("Listening for connections on port %1").arg(m_setupPort)); 130 135 break; 131 136 } 137 132 138 m_setupPort++; 133 139 } 134 140 … … void MythRAOPDevice::Start(void) 137 143 138 144 // announce service 139 145 m_bonjour = new BonjourRegister(this); 146 140 147 if (!m_bonjour) 141 148 { 142 149 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create Bonjour object."); … … void MythRAOPDevice::Start(void) 145 152 146 153 // give each frontend a unique name 147 154 int multiple = m_setupPort - baseport; 155 148 156 if (multiple > 0) 149 157 m_name += QString::number(multiple); 150 158 … … void MythRAOPDevice::Start(void) 164 172 txt.append(4); txt.append("ch=2"); 165 173 txt.append(5); txt.append("ss=16"); 166 174 txt.append(8); txt.append("sr=44100"); 167 txt.append(8); txt.append("pw=false"); 175 176 if (gCoreContext->GetNumSettingOnHost("RAOPPasswordEnabled", 177 gCoreContext->GetHostName(), 178 0)) 179 { 180 txt.append(7); txt.append("pw=true"); 181 } 182 else 183 { 184 txt.append(8); txt.append("pw=false"); 185 } 186 168 187 txt.append(4); txt.append("vn=3"); 169 188 txt.append(9); txt.append("txtvers=1"); 170 189 … … void MythRAOPDevice::newConnection(void) 186 205 .arg(client->peerAddress().toString()).arg(client->peerPort())); 187 206 188 207 int port = 6000; 208 189 209 while (port < (6000 + RAOP_PORT_RANGE)) 190 210 { 191 211 bool found = false; 192 foreach (MythRAOPConnection* client, m_clients)212 foreach(MythRAOPConnection * client, m_clients) 193 213 { 194 214 if (client->GetDataPort() == port) 195 215 { … … void MythRAOPDevice::newConnection(void) 197 217 port++; 198 218 } 199 219 } 220 200 221 if (!found) 201 222 break; 202 223 } 203 224 204 225 MythRAOPConnection *obj = 205 new MythRAOPConnection(client, m_hardwareId, port); 226 new MythRAOPConnection(client, m_hardwareId, port); 227 206 228 if (obj->Init()) 207 229 { 208 230 m_clients.append(obj); … … void MythRAOPDevice::deleteClient(void) 220 242 { 221 243 QMutexLocker locker(m_lock); 222 244 QList<MythRAOPConnection *>::iterator it; 245 223 246 for (it = m_clients.begin(); it != m_clients.end(); ++it) 224 247 { 225 248 if ((*it)->GetSocket()->state() == QTcpSocket::UnconnectedState) -
mythtv/programs/mythfrontend/globalsettings.cpp
diff --git a/mythtv/programs/mythfrontend/globalsettings.cpp b/mythtv/programs/mythfrontend/globalsettings.cpp index 7e6fad3..e864968 100644
a b static HostLineEdit *UDPNotifyPort() 2680 2680 return ge; 2681 2681 } 2682 2682 2683 static HostCheckBox *RAOPPasswordEnabled() 2684 { 2685 HostCheckBox *gc = new HostCheckBox("RAOPPasswordEnabled"); 2686 gc->setLabel(QObject::tr("Enable password protection for RAOP")); 2687 gc->setHelpText(QObject::tr("This enables password protection for devices" 2688 "connecting via RAOP. (Requires mythfrontend restart)" 2689 )); 2690 gc->setValue(false); 2691 return gc; 2692 } 2693 2694 static HostLineEdit *RAOPPassword() 2695 { 2696 HostLineEdit *ge = new HostLineEdit("RAOPPassword"); 2697 ge->setLabel(QObject::tr("RAOP Access Password")); 2698 2699 ge->setValue(""); 2700 QString help = QObject::tr( 2701 "Enter a password."); 2702 ge->setHelpText(help); 2703 return ge; 2704 } 2705 2683 2706 static HostCheckBox *RealtimePriority() 2684 2707 { 2685 2708 HostCheckBox *gc = new HostCheckBox("RealtimePriority"); … … MainGeneralSettings::MainGeneralSettings() 3295 3318 remotecontrol->addChild(NetworkControlEnabled()); 3296 3319 remotecontrol->addChild(NetworkControlPort()); 3297 3320 remotecontrol->addChild(UDPNotifyPort()); 3321 remotecontrol->addChild(RAOPPasswordEnabled()); 3322 remotecontrol->addChild(RAOPPassword()); 3298 3323 addChild(remotecontrol); 3299 3324 }