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