FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/rcwtenc.c
Date: 2024-05-03 15:42:48
Exec Total Coverage
Lines: 42 49 85.7%
Functions: 5 5 100.0%
Branches: 9 16 56.2%

Line Branch Exec Source
1 /*
2 * RCWT (Raw Captions With Time) muxer
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /*
22 * RCWT (Raw Captions With Time) is a format native to ccextractor, a commonly
23 * used open source tool for processing 608/708 Closed Captions (CC) sources.
24 *
25 * This muxer implements the specification as of March 2024, which has
26 * been stable and unchanged since April 2014.
27 *
28 * This muxer will have some nuances from the way that ccextractor muxes RCWT.
29 * No compatibility issues when processing the output with ccextractor
30 * have been observed as a result of this so far, but mileage may vary
31 * and outputs will not be a bit-exact match.
32 *
33 * Specifically, the differences are:
34 * (1) This muxer will identify as "FF" as the writing program identifier, so
35 * as to be honest about the output's origin.
36 *
37 * (2) This muxer will not alter the extracted data except to remove invalid
38 * packets in between valid CC blocks. On the other hand, ccextractor
39 * will by default remove mid-stream padding, and add padding at the end
40 * of the stream (in order to convey the end time of the source video).
41 *
42 * A free specification of RCWT can be found here:
43 * @url{https://github.com/CCExtractor/ccextractor/blob/master/docs/BINARY_FILE_FORMAT.TXT}
44 */
45
46 #include "avformat.h"
47 #include "internal.h"
48 #include "mux.h"
49 #include "libavutil/log.h"
50 #include "libavutil/intreadwrite.h"
51
52 #define RCWT_CLUSTER_MAX_BLOCKS 65535
53 #define RCWT_BLOCK_SIZE 3
54
55 typedef struct RCWTContext {
56 int cluster_pos;
57 int64_t cluster_pts;
58 uint8_t cluster_buf[RCWT_CLUSTER_MAX_BLOCKS * RCWT_BLOCK_SIZE];
59 } RCWTContext;
60
61 122 static void rcwt_init_cluster(RCWTContext *rcwt)
62 {
63 122 rcwt->cluster_pos = 0;
64 122 rcwt->cluster_pts = AV_NOPTS_VALUE;
65 122 }
66
67 121 static void rcwt_flush_cluster(AVFormatContext *avf)
68 {
69 121 RCWTContext *rcwt = avf->priv_data;
70
71
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 1 times.
121 if (rcwt->cluster_pos > 0) {
72 120 avio_wl64(avf->pb, rcwt->cluster_pts);
73 120 avio_wl16(avf->pb, rcwt->cluster_pos / RCWT_BLOCK_SIZE);
74 120 avio_write(avf->pb, rcwt->cluster_buf, rcwt->cluster_pos);
75 }
76
77 121 rcwt_init_cluster(rcwt);
78 121 }
79
80 1 static int rcwt_write_header(AVFormatContext *avf)
81 {
82 1 avpriv_set_pts_info(avf->streams[0], 64, 1, 1000);
83
84 /* magic number */
85 1 avio_wb16(avf->pb, 0xCCCC);
86 1 avio_w8(avf->pb, 0xED);
87
88 /* program version (identify as ffmpeg) */
89 1 avio_wb16(avf->pb, 0xFF00);
90 1 avio_w8(avf->pb, 0x60);
91
92 /* format version, only version 0.001 supported for now */
93 1 avio_wb16(avf->pb, 0x0001);
94
95 /* reserved */
96 1 avio_wb16(avf->pb, 0x000);
97 1 avio_w8(avf->pb, 0x00);
98
99 1 rcwt_init_cluster(avf->priv_data);
100
101 1 return 0;
102 }
103
104 120 static int rcwt_write_packet(AVFormatContext *avf, AVPacket *pkt)
105 {
106 120 RCWTContext *rcwt = avf->priv_data;
107
108
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 if (pkt->size < RCWT_BLOCK_SIZE)
109 return 0;
110
111 /* new PTS, new cluster */
112
1/2
✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
120 if (pkt->pts != rcwt->cluster_pts) {
113 120 rcwt_flush_cluster(avf);
114 120 rcwt->cluster_pts = pkt->pts;
115 }
116
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 if (pkt->pts == AV_NOPTS_VALUE) {
118 av_log(avf, AV_LOG_WARNING, "Ignoring CC packet with no PTS\n");
119 return 0;
120 }
121
122
2/2
✓ Branch 0 taken 2848 times.
✓ Branch 1 taken 120 times.
2968 for (int i = 0; i <= pkt->size - RCWT_BLOCK_SIZE;) {
123 uint8_t cc_valid;
124 uint8_t cc_type;
125
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2848 times.
2848 if (rcwt->cluster_pos == RCWT_CLUSTER_MAX_BLOCKS * RCWT_BLOCK_SIZE) {
127 av_log(avf, AV_LOG_WARNING, "Starting new cluster due to size\n");
128 rcwt_flush_cluster(avf);
129 }
130
131 2848 cc_valid = (pkt->data[i] & 0x04) >> 2;
132 2848 cc_type = pkt->data[i] & 0x03;
133
134
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2848 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2848 if (!(cc_valid || cc_type == 3)) {
135 i++;
136 continue;
137 }
138
139 2848 memcpy(&rcwt->cluster_buf[rcwt->cluster_pos], &pkt->data[i], 3);
140 2848 rcwt->cluster_pos += 3;
141 2848 i += 3;
142 }
143
144 120 return 0;
145 }
146
147 1 static int rcwt_write_trailer(AVFormatContext *avf)
148 {
149 1 rcwt_flush_cluster(avf);
150
151 1 return 0;
152 }
153
154 const FFOutputFormat ff_rcwt_muxer = {
155 .p.name = "rcwt",
156 .p.long_name = NULL_IF_CONFIG_SMALL("RCWT (Raw Captions With Time)"),
157 .p.flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT,
158 .p.video_codec = AV_CODEC_ID_NONE,
159 .p.audio_codec = AV_CODEC_ID_NONE,
160 .p.subtitle_codec = AV_CODEC_ID_EIA_608,
161 .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH |
162 FF_OFMT_FLAG_ONLY_DEFAULT_CODECS,
163 .priv_data_size = sizeof(RCWTContext),
164 .write_header = rcwt_write_header,
165 .write_packet = rcwt_write_packet,
166 .write_trailer = rcwt_write_trailer
167 };
168