1 /*
2 * TheoraEncoder wrapper
3 *
4 * Copyright (C) 2008 Joern Seger
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10
11 * This program 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
14 * GNU General Public License for more details.
15
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 *
20 */
21
22 #include "theoraEncoder.h"
23
24 #ifdef HAVE_LIBTHEORADEC
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include <iostream>
31 #include <sstream>
32 #include <cstdlib>
33 #include <cstring>
34 #include <ogg/ogg.h>
35
36 #include "exception.h"
37 #include "log.h"
38
TheoraEncoder(uint8 _streamNo)39 TheoraEncoder::TheoraEncoder(uint8 _streamNo)
40 : MediaInputEncoder(_streamNo), packetCounter(0)
41 {
42 th_comment_init(&theoraComment);
43 }
44
~TheoraEncoder()45 TheoraEncoder::~TheoraEncoder()
46 {
47 if (isConfigured())
48 th_encode_free(theoraState);
49
50 th_info_clear(&theoraInfo);
51 th_comment_clear(&theoraComment);
52
53 }
54
createHeader(std::vector<OggPacket> & headerList,std::vector<OggComment> & oggComments)55 void TheoraEncoder::createHeader(std::vector<OggPacket>& headerList, std::vector<OggComment>& oggComments)
56 {
57 int32 encodeRetID(1);
58
59 th_comment_add_tag(&theoraComment,"ENCODER",PACKAGE_STRING);
60
61 /* add other comments */
62 for (uint32 i(0); i<oggComments.size(); ++i)
63 th_comment_add_tag(&theoraComment, (char*) oggComments[i].tag.c_str(), (char*) oggComments[i].value.c_str());
64
65 while (encodeRetID > 0) {
66 ogg_packet tmpPkt;
67 encodeRetID = th_encode_flushheader(theoraState, &theoraComment, &tmpPkt); //packet->getUnderlayingOggPacketPtr());
68
69 if (encodeRetID == TH_EFAULT)
70 throw OggException("TheoraEncoder::operator <<: encoder or packet are NULL");
71
72 // ost::slog(ost::Slog::levelDebug) << "TheoraEncoder:: inserting header/n";
73 if (encodeRetID > 0) {
74 OggPacket packet = std::make_shared<OggPacketInternal>(tmpPkt);
75
76 #ifdef DEBUG
77 logger.debug() << "Theora Packet Number: "<< packet.packetno << "reset to 0" << std::endl;
78 #endif
79
80 packet->setStreamType(OggType::theora);
81 packet->setStreamNo(streamNo);
82 packet->setStreamHeader();
83 packet->setPacketno(0);
84
85 headerList.push_back(packet);
86 }
87 }
88
89
90 }
91
reset()92 void TheoraEncoder::reset()
93 {
94 if (isConfigured()) {
95 th_encode_free(theoraState);
96 theoraState = th_encode_alloc(&theoraInfo);
97 }
98
99 }
100
configureEncoder(StreamConfig & streamConf,std::vector<OggComment> & oggComments)101 void TheoraEncoder::configureEncoder(StreamConfig& streamConf, std::vector<OggComment>& oggComments)
102 {
103 if (isConfigured())
104 throw OggException("TheoraEncoder::configureEncoder: can't configure encoder twice");
105
106 TheoraStreamParameter& config = dynamic_cast<TheoraStreamParameter&>(*streamConf.parameter.get());
107
108 // Theora has a divisible-by-sixteen restriction for the encoded video size
109 // scale the frame size up to the nearest /16 and calculate offsets
110
111 config.frameX = (config.pictureX+15)&~0xF;
112 config.frameY = (config.pictureY+15)&~0xF;
113
114 // We force the offset to be even.
115 // This ensures that the chroma samples align properly with the luma
116 // samples.
117
118 // config.frameXOffset = ((config.frameX - config.pictureX)/2)&~1;
119 // config.frameYOffset = ((config.frameY - config.pictureY)/2)&~1;
120 // config.frameXOffset = 0;
121 // config.frameYOffset = 0;
122
123 // let's initialize the theora encoder
124 th_info_init(&theoraInfo);
125
126 theoraInfo.pic_width = config.pictureX;
127 theoraInfo.pic_height = config.pictureY;
128 theoraInfo.frame_width = config.frameX;
129 theoraInfo.frame_height = config.frameY;
130 theoraInfo.pic_x = config.frameXOffset;
131 theoraInfo.pic_y = config.frameYOffset;
132 theoraInfo.fps_numerator = config.framerateNum;
133 theoraInfo.fps_denominator = config.framerateDenom;
134 theoraInfo.aspect_numerator = config.aspectRatioNum;
135 theoraInfo.aspect_denominator = config.aspectRatioDenom;
136 switch ( config.colorspace ) {
137 case TheoraStreamParameter::ITU_470M:
138 theoraInfo.colorspace = TH_CS_ITU_REC_470M;
139 break;
140 case TheoraStreamParameter::ITU_470BG:
141 theoraInfo.colorspace = TH_CS_ITU_REC_470BG;
142 break;
143 default:
144 theoraInfo.colorspace = TH_CS_UNSPECIFIED;
145 break;
146 }
147 switch (config.pixel_fmt) {
148 case TheoraStreamParameter::pf_420:
149 theoraInfo.pixel_fmt = TH_PF_420;
150 break;
151 case TheoraStreamParameter::pf_422:
152 theoraInfo.pixel_fmt = TH_PF_422;
153 break;
154 case TheoraStreamParameter::pf_444:
155 theoraInfo.pixel_fmt = TH_PF_444;
156 break;
157 default:
158 theoraInfo.pixel_fmt = TH_PF_420; // most likly this format
159 break;
160 }
161
162 theoraInfo.target_bitrate = config.videoBitrate;
163 theoraInfo.quality = config.videoQuality;
164 theoraInfo.keyframe_granule_shift = config.keyframeShift; // 6 bit to distinguish interframes
165
166 // TODO: Pixel Format should be available in config
167 /* create a new theora encoder handle */
168 theoraState = th_encode_alloc(&theoraInfo);
169
170 if (theoraState)
171 setConfigured();
172 else
173 throw OggException("TheoraEncoder::setConfig: Parameters invalid");
174
175 createHeader(streamConf.headerList, oggComments);
176
177 streamConf.serialNo = (uint32) rand();
178 streamConf.streamNo = streamNo;
179 streamConf.type = OggType::theora;
180 streamConf.numOfHeaderPackets = (uint8) streamConf.headerList.size();
181
182 // reset the packet counter if encoder is reconfigured
183 packetCounter = 0;
184 }
185
operator >>(OggPacket & packet)186 MediaInputEncoder& TheoraEncoder::operator >>(OggPacket& packet)
187 {
188 if (packetList.empty())
189 throw OggException("TheoraEncoder::operator >>: No PacketAvailable");
190
191 packet = packetList.front();
192 packetList.pop_front();
193
194 packet->setPacketno(packetCounter++);
195
196 if (packetList.empty())
197 setEmpty();
198
199 return(*this);
200 }
201
operator <<(th_ycbcr_buffer buffer)202 MediaInputEncoder& TheoraEncoder::operator <<(th_ycbcr_buffer buffer)
203 {
204 if (!isConfigured())
205 throw OggException("TheoraEncoder::operator <<: codec not configured");
206
207 int32 errID;
208 if ((errID = th_encode_ycbcr_in(theoraState, buffer)) != 0) {
209 if (errID == TH_EFAULT)
210 throw OggException("TheoraEncoder::operator <<: encoder or video buffer is NULL");
211 if (errID == TH_EINVAL) {
212 logger.debug() << "Size of picture "<<buffer[0].width << " x " << buffer[0].height<< " encoder wants "
213 << std::endl;
214 throw OggException("TheoraEncoder::operator <<: buffer size does not match the frame size the encoder was initialized with, or encoding has already completed");
215 }
216 }
217
218 int32 encodeRetID(1);
219
220 while ( encodeRetID > 0) {
221
222 ogg_packet _packet;
223 encodeRetID = th_encode_packetout(theoraState, 0, &_packet);
224
225 /* Spec says:
226 * _op An ogg_packet structure to fill. All of the elements of this structure will be set,
227 * including a pointer to the video data. The memory for the video data is owned by libtheoraenc,
228 * and may be invalidated when the next encoder function is called.
229 * This means, data may be lost and cannot be used in a packet list, so we need to copy,
230 * this really kills performance ... */
231 if (encodeRetID > 0) {
232 OggPacket packet = std::make_shared<OggPacketInternal>(_packet);
233
234 #ifdef DEBUG
235 logger.debug() << "Theora Packet Number: "<< packet.packetno<<std::endl;
236 logger.debug() << "Theora Packet Length: "<< packet.bytes<<std::endl;
237 #endif
238
239 packet->setStreamType(OggType::theora);
240 packet->setStreamNo(streamNo);
241
242 packetList.push_back(packet);
243 }
244 }
245
246 if (encodeRetID == TH_EFAULT)
247 throw OggException("TheoraEncoder::operator <<: encoder or packet are NULL");
248
249 setAvailable();
250
251 return(*this);
252 }
253
width() const254 uint32 TheoraEncoder::width() const
255 {
256 return(theoraInfo.pic_width);
257 }
258
height() const259 uint32 TheoraEncoder::height() const
260 {
261 return(theoraInfo.pic_height);
262 }
263
getInfo()264 th_info& TheoraEncoder::getInfo()
265 {
266 return(theoraInfo);
267 }
268
configuration() const269 std::string TheoraEncoder::configuration() const
270 {
271 std::stringstream stream;
272
273 stream << "Theora Encoder Configuration:"<<std::endl;
274 stream << "Stream No: "<<(int)streamNo<<std::endl;
275 stream << std::endl;
276 stream << "Theora Version : " << (int) theoraInfo.version_major << "." << (int) theoraInfo.version_minor
277 << "." << (int) theoraInfo.version_subminor << std::endl;
278 stream << std::endl;
279 stream << "Video Size : " << theoraInfo.pic_width << " x " << theoraInfo.pic_height<<std::endl;
280
281 if ((theoraInfo.pic_width != theoraInfo.frame_width) ||
282 (theoraInfo.pic_height != theoraInfo.frame_height)) {
283
284 stream << " - Frame Size : " << theoraInfo.frame_width << " x " << theoraInfo.frame_height << std::endl;
285 stream << " - Offset : " << theoraInfo.pic_x << " x " << theoraInfo.pic_y << std::endl;
286
287 }
288
289 stream << "Keyframe Shift : " <<(uint32)(1 << theoraInfo.keyframe_granule_shift)<< " frames " << std::endl;
290 stream << "Aspect Ratio : " << theoraInfo.aspect_numerator << " : " << theoraInfo.aspect_denominator << std::endl;
291 stream << "Framerate : " << theoraInfo.fps_numerator << " / " << theoraInfo.fps_denominator << "\n";
292 stream << std::endl;
293 stream << "Quality : " << theoraInfo.quality << " / 64" << std::endl;
294 stream << "Datarate : " << theoraInfo.target_bitrate << std::endl;
295 stream << "Pixel Format : ";
296 switch (theoraInfo.pixel_fmt) {
297 case TH_PF_420:
298 stream << "420 (Chroma decimination by 2 in both directions)"<<std::endl;
299 break;
300 case TH_PF_422:
301 stream << "422 (Chroma decimination by 2 in X direction)"<<std::endl;
302 break;
303 case TH_PF_444:
304 stream << "444 (No Chroma decimination)"<<std::endl;
305 break;
306 default:
307 stream << " unknown"<<std::endl;
308 break;
309 }
310 stream << "Colorspace : ";
311 switch (theoraInfo.colorspace) {
312 case TH_CS_ITU_REC_470M:
313 stream << "ITU Rec. 470M (designed for NTSC content)"<<std::endl;
314 break;
315 case TH_CS_ITU_REC_470BG:
316 stream << "ITU Rec. 470BG (designed for PAL/SECAM content)"<<std::endl;
317 break;
318 default:
319 stream << "unspecified"<<std::endl;
320 break;
321 }
322
323 stream << std::endl;
324
325 if (theoraComment.comments) {
326 stream << "Comments:\n";
327 for (int i=0; i<theoraComment.comments; ++i)
328 stream << theoraComment.user_comments[i] << std::endl;
329
330 stream << std::endl;
331 }
332
333 return(stream.str());
334 }
335
336 #endif
337
338
339