|
61 | 61 | import static org.mockito.Mockito.verify; |
62 | 62 | import static org.mockito.Mockito.when; |
63 | 63 |
|
| 64 | +<<<<<<< HEAD |
64 | 65 | import java.io.File; |
65 | 66 | import java.io.FileInputStream; |
66 | 67 | import java.io.FileOutputStream; |
|
80 | 81 | import java.util.concurrent.TimeUnit; |
81 | 82 | import java.util.concurrent.atomic.AtomicBoolean; |
82 | 83 | import io.antmedia.*; |
| 84 | +======= |
| 85 | +import java.lang.reflect.Field; |
| 86 | +>>>>>>> origin/master |
83 | 87 | import com.google.gson.JsonObject; |
84 | 88 | import io.antmedia.AntMediaApplicationAdapter; |
85 | 89 | import io.antmedia.AppSettings; |
@@ -1068,7 +1072,7 @@ public void testGetAudioCodecParameters() { |
1068 | 1072 | } |
1069 | 1073 |
|
1070 | 1074 | @Test |
1071 | | - public void testStopRtmpStreamingWhenRtmpMuxerNull() { |
| 1075 | + public void testStopRtmpStreamingWhenEndpointMuxerNull() { |
1072 | 1076 | appScope = (WebScope) applicationContext.getBean("web.scope"); |
1073 | 1077 | logger.info("Application / web scope: {}", appScope); |
1074 | 1078 | assertTrue(appScope.getDepth() == 1); |
@@ -1402,6 +1406,72 @@ public void testRTMPPrepareIO() { |
1402 | 1406 | verify(endpointMuxer2).clearResource(); |
1403 | 1407 | } |
1404 | 1408 |
|
| 1409 | + @Test |
| 1410 | + public void testRTMPMuxerRaceCondition() { |
| 1411 | + appScope = (WebScope) applicationContext.getBean("web.scope"); |
| 1412 | + vertx = (Vertx) appScope.getContext().getApplicationContext().getBean(IAntMediaStreamHandler.VERTX_BEAN_NAME); |
| 1413 | + |
| 1414 | + EndpointMuxer rtmpMuxer = new RtmpMuxer("rtmp://no_server", vertx); |
| 1415 | + rtmpMuxer.init(appScope, "test", 0, null, 0); |
| 1416 | + |
| 1417 | + AVCodecParameters codecParameters = new AVCodecParameters(); |
| 1418 | + codecParameters.codec_id(AV_CODEC_ID_H264); |
| 1419 | + codecParameters.codec_type(AVMEDIA_TYPE_VIDEO); |
| 1420 | + AVRational rat = new AVRational().num(1).den(1000); |
| 1421 | + assertTrue(rtmpMuxer.addStream(codecParameters, rat, 50)); |
| 1422 | + |
| 1423 | + // 1. Test preparedIO reset in clearResource |
| 1424 | + assertTrue(rtmpMuxer.prepareIO()); |
| 1425 | + assertFalse(rtmpMuxer.prepareIO()); // already prepared |
| 1426 | + |
| 1427 | + rtmpMuxer.clearResource(); // headerWritten is false, resets preparedIO |
| 1428 | + |
| 1429 | + // Re-add stream because clearResource cleared outputFormatContext |
| 1430 | + assertTrue(rtmpMuxer.addStream(codecParameters, rat, 50)); |
| 1431 | + assertTrue(rtmpMuxer.prepareIO()); // can prepare again |
| 1432 | + |
| 1433 | + // 2. Test cancelOpenIO race protection |
| 1434 | + EndpointMuxer rtmpMuxer2 = new RtmpMuxer("rtmp://no_server", vertx); |
| 1435 | + rtmpMuxer2.init(appScope, "test", 0, null, 0); |
| 1436 | + assertTrue(rtmpMuxer2.addStream(codecParameters, rat, 50)); |
| 1437 | + |
| 1438 | + // Start prepareIO which will call openIO in blocking thread |
| 1439 | + rtmpMuxer2.prepareIO(); |
| 1440 | + // Immediately cancel it |
| 1441 | + rtmpMuxer2.writeTrailer(); |
| 1442 | + |
| 1443 | + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> { |
| 1444 | + String status = rtmpMuxer2.getStatus(); |
| 1445 | + return status.equals(IAntMediaStreamHandler.BROADCAST_STATUS_FINISHED) || status.equals(IAntMediaStreamHandler.BROADCAST_STATUS_FAILED); |
| 1446 | + }); |
| 1447 | + |
| 1448 | + // isRunning should remain false because it was cancelled |
| 1449 | + assertFalse(rtmpMuxer2.getIsRunning().get()); |
| 1450 | + } |
| 1451 | + |
| 1452 | + @Test |
| 1453 | + public void testWriteTrailerBeforeHeader() { |
| 1454 | + appScope = (WebScope) applicationContext.getBean("web.scope"); |
| 1455 | + vertx = (Vertx) appScope.getContext().getApplicationContext().getBean(IAntMediaStreamHandler.VERTX_BEAN_NAME); |
| 1456 | + |
| 1457 | + EndpointMuxer rtmpMuxer = new RtmpMuxer("rtmp://no_server", vertx); |
| 1458 | + rtmpMuxer.init(appScope, "test", 0, null, 0); |
| 1459 | + |
| 1460 | + // No header written yet |
| 1461 | + rtmpMuxer.writeTrailer(); |
| 1462 | + |
| 1463 | + assertEquals(IAntMediaStreamHandler.BROADCAST_STATUS_FINISHED, rtmpMuxer.getStatus()); |
| 1464 | + |
| 1465 | + AVCodecParameters codecParameters = new AVCodecParameters(); |
| 1466 | + codecParameters.codec_id(AV_CODEC_ID_H264); |
| 1467 | + codecParameters.codec_type(AVMEDIA_TYPE_VIDEO); |
| 1468 | + AVRational rat = new AVRational().num(1).den(1000); |
| 1469 | + |
| 1470 | + // It should be able to add stream and prepareIO again because clearResource was called in writeTrailer |
| 1471 | + assertTrue(rtmpMuxer.addStream(codecParameters, rat, 50)); |
| 1472 | + assertTrue(rtmpMuxer.prepareIO()); |
| 1473 | + } |
| 1474 | + |
1405 | 1475 |
|
1406 | 1476 | @Test |
1407 | 1477 | public void testRTMPHealthCheckProcess() { |
@@ -6646,7 +6716,70 @@ public void testPrepareFromInputFormatContextForData() throws Exception { |
6646 | 6716 | assertEquals(1, muxAdaptor.getVideoStreamIndex()); |
6647 | 6717 | assertEquals(2, muxAdaptor.getAudioStreamIndex()); |
6648 | 6718 | } |
6649 | | - |
| 6719 | + |
| 6720 | + @Test |
| 6721 | + public void testEndpointMuxerPrepareIOCancelledAndNotCancelled() throws Exception { |
| 6722 | + appScope = (WebScope) applicationContext.getBean("web.scope"); |
| 6723 | + vertx = (Vertx) appScope.getContext().getApplicationContext().getBean(IAntMediaStreamHandler.VERTX_BEAN_NAME); |
| 6724 | + |
| 6725 | + // Scenario 1: Test Cancellation (Lines 246-253 and 151-164 cancellation path) |
| 6726 | + EndpointMuxer endpointMuxer = Mockito.spy(new EndpointMuxer("rtmp://dummy", vertx)); |
| 6727 | + |
| 6728 | + // Mock getOutputFormatContext to return a context with streams |
| 6729 | + AVFormatContext outputFormatContext = new AVFormatContext(null); |
| 6730 | + avformat_alloc_output_context2(outputFormatContext, null, "flv", null); |
| 6731 | + avformat_new_stream(outputFormatContext, null); |
| 6732 | + Mockito.doReturn(outputFormatContext).when(endpointMuxer).getOutputFormatContext(); |
| 6733 | + |
| 6734 | + // Mock openIO to set cancelOpenIO = true and return true |
| 6735 | + Mockito.doAnswer(invocation -> { |
| 6736 | + Field cancelField = EndpointMuxer.class.getDeclaredField("cancelOpenIO"); |
| 6737 | + cancelField.setAccessible(true); |
| 6738 | + AtomicBoolean cancel = (AtomicBoolean) cancelField.get(endpointMuxer); |
| 6739 | + cancel.set(true); |
| 6740 | + return true; |
| 6741 | + }).when(endpointMuxer).openIO(); |
| 6742 | + |
| 6743 | + // Add element to bsfFilterContextList so it enters the check block |
| 6744 | + Field bsfField = Muxer.class.getDeclaredField("bsfFilterContextList"); |
| 6745 | + bsfField.setAccessible(true); |
| 6746 | + List<AVBSFContext> bsfList = (List<AVBSFContext>) bsfField.get(endpointMuxer); |
| 6747 | + bsfList.add(new AVBSFContext(null)); |
| 6748 | + |
| 6749 | + endpointMuxer.prepareIO(); |
| 6750 | + |
| 6751 | + Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> { |
| 6752 | + // preparedIO should be false because clearResource is called |
| 6753 | + Field preparedField = EndpointMuxer.class.getDeclaredField("preparedIO"); |
| 6754 | + preparedField.setAccessible(true); |
| 6755 | + AtomicBoolean prepared = (AtomicBoolean) preparedField.get(endpointMuxer); |
| 6756 | + return !prepared.get() && !endpointMuxer.getIsRunning().get(); |
| 6757 | + }); |
| 6758 | + |
| 6759 | + Mockito.verify(endpointMuxer, Mockito.atLeastOnce()).clearResource(); |
| 6760 | + |
| 6761 | + |
| 6762 | + // Scenario 2: Test Normal Flow (Lines 151-164 normal path) |
| 6763 | + EndpointMuxer rtmpMuxer2 = Mockito.spy(new EndpointMuxer("rtmp://dummy2", vertx)); |
| 6764 | + Mockito.doReturn(outputFormatContext).when(rtmpMuxer2).getOutputFormatContext(); |
| 6765 | + Mockito.doReturn(true).when(rtmpMuxer2).openIO(); |
| 6766 | + |
| 6767 | + bsfList = (List<AVBSFContext>) bsfField.get(rtmpMuxer2); |
| 6768 | + bsfList.add(new AVBSFContext(null)); |
| 6769 | + |
| 6770 | + rtmpMuxer2.prepareIO(); |
| 6771 | + |
| 6772 | + Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> { |
| 6773 | + return rtmpMuxer2.getIsRunning().get() |
| 6774 | + && rtmpMuxer2.getStatus().equals(IAntMediaStreamHandler.BROADCAST_STATUS_BROADCASTING); |
| 6775 | + }); |
| 6776 | + |
| 6777 | + assertTrue(rtmpMuxer2.getIsRunning().get()); |
| 6778 | + assertEquals(IAntMediaStreamHandler.BROADCAST_STATUS_BROADCASTING, rtmpMuxer2.getStatus()); |
| 6779 | + |
| 6780 | + // Clean up |
| 6781 | + avformat_free_context(outputFormatContext); |
| 6782 | + } |
6650 | 6783 |
|
6651 | 6784 |
|
6652 | 6785 |
|
|
0 commit comments