1 /*
2 
3    Ming, an SWF output library
4    Copyright (C) 2002  Opaque Industries - http://www.opaque.net/
5 
6    15.12.2003 Klaus Rechert
7 
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Lesser General Public
10    License as published by the Free Software Foundation; either
11    version 2.1 of the License, or (at your option) any later version.
12 
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Lesser General Public License for more details.
17 
18    You should have received a copy of the GNU Lesser General Public
19    License along with this library; if not, write to the Free Software
20    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 
22 */
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 
27 #include "input.h"
28 #include "method.h"
29 #include "block.h"
30 #include "character.h"
31 #include "videostream.h"
32 #include "libming.h"
33 #include "../displaylist.h"
34 
35 #include "flv.h"
36 
37 #define VIDEO_SMOOTHING 0x01
38 #define VIDEO_NO_SMOOTHING 0x0
39 #define VIDEOFRAME_BLOCK_SIZE 10
40 
41 struct SWFVideoStream_s
42 {
43 	struct SWFCharacter_s character;
44 
45 	FLVStream *flv;
46 	FLVTag *lastTag;
47 	int lastFrame;
48 
49 	int numFrames;
50 	unsigned int frame;
51 	int width;
52 	int height;
53 	unsigned short embedded;
54 	unsigned char codecId;
55 	unsigned char smoothingFlag;
56 	int mode;
57 	int addFrame;
58 	int framesLoaded;
59 	int firstFrame;
60 };
61 
62 struct SWFVideoFrame_s
63 {
64 	struct SWFBlock_s block;
65 
66 	SWFVideoStream stream;
67 	int frameNum;
68 
69 	FLVTag tag;
70 };
71 
SWFVideoStream_getFrameNumber(SWFVideoFrame frame)72 int SWFVideoStream_getFrameNumber(SWFVideoFrame frame)
73 {
74 	return frame->frameNum;
75 }
76 
completeSWFVideoFrame(SWFBlock block)77 int completeSWFVideoFrame(SWFBlock block)
78 {
79 	SWFVideoFrame frame = (SWFVideoFrame)block;
80 	SWFInput input;
81 
82 	input = FLVTag_getPayloadInput(&frame->tag);
83 	if(input == NULL)
84 		return 4;
85 
86 	return SWFInput_length(input) + 4;
87 }
88 
writeSWFVideoFrameToMethod(SWFBlock block,SWFByteOutputMethod method,void * data)89 void writeSWFVideoFrameToMethod(SWFBlock block, SWFByteOutputMethod method, void *data)
90 {
91 	int i, ichar, len;
92 	SWFVideoFrame frame = (SWFVideoFrame)block;
93 	SWFVideoStream stream;
94 	SWFInput input;
95 
96 	if(!block)
97 		return;
98 
99 	input = FLVTag_getPayloadInput(&frame->tag);
100 	if(input == NULL)
101 		return;
102 	len = SWFInput_length(input);
103 	stream = frame->stream;
104 
105 	methodWriteUInt16(CHARACTERID(stream),method, data);
106 	methodWriteUInt16(frame->frameNum, method, data);
107 
108 	for (i = 0; i < len; i++) {
109 		ichar = SWFInput_getChar(input);
110 		method(ichar, data);
111 	}
112 }
113 
114 /*
115  * embedes a video frame in a SWFBlock object
116  * This function reads a video frame for a FLV source and embeds it in a
117  * SWFBlock object.
118  */
119 SWFBlock
SWFVideoStream_getVideoFrame(SWFVideoStream stream)120 SWFVideoStream_getVideoFrame(SWFVideoStream stream /* associated video stream */) {
121 	SWFVideoFrame block;
122 	int frame;
123 
124 	if(!stream->embedded)
125 		return NULL;
126 
127 	if(stream->frame >= stream->numFrames)
128 	{
129 		SWF_warn("SWFVideoStream_getVideoFrame: frame %i, numFrames %i\n",
130 			stream->frame, stream->numFrames);
131 		return NULL;
132 	}
133 
134 	if(stream->frame < stream->framesLoaded)
135 		return NULL;
136 
137 	block = (SWFVideoFrame) malloc(sizeof(struct SWFVideoFrame_s));
138 
139 	/* If malloc failed, return NULL to signify this */
140 	if (NULL == block)
141 		return NULL;
142 
143 	SWFBlockInit((SWFBlock)block);
144 
145 	BLOCK(block)->complete = completeSWFVideoFrame;
146 	BLOCK(block)->writeBlock = writeSWFVideoFrameToMethod;
147 	BLOCK(block)->dtor = NULL;
148 	BLOCK(block)->type = SWF_VIDEOFRAME;
149 
150 	block->stream = stream;
151 	if(stream->lastTag != NULL && stream->lastFrame < stream->frame)
152 	{
153 		frame = stream->lastFrame;
154 	}
155 	else
156 	{
157 		stream->lastTag = NULL;
158 		frame = -1;
159 	}
160 
161 	do {
162 		if(FLVStream_nextTag(stream->flv, &block->tag, stream->lastTag))
163 		{
164 			free(block);
165 			return NULL;
166 		}
167 		stream->lastTag = &block->tag;
168 		if(block->tag.tagType == FLV_VIDEOTAG)
169 		{
170 			frame++;
171 		}
172 	} while (frame != stream->frame);
173 
174 	block->frameNum = stream->frame;
175 	stream->lastFrame = stream->frame;
176 	stream->framesLoaded = stream->frame + 1;
177 	return (SWFBlock)block;
178 }
179 
180 
setH263CustomDimension(SWFVideoStream stream,SWFInput input,int flags)181 static int setH263CustomDimension(SWFVideoStream stream, SWFInput input, int flags)
182 {
183 	int ichar, rshift, mask;
184 	int (*method) (SWFInput stream);
185 
186 	SWFInput_seek(input, -1, SEEK_CUR);
187 	if(flags == 0) {
188 		method = SWFInput_getChar;
189 		rshift = 7;
190 		mask = 0xff;
191 	}
192 	else if (flags == 1) {
193 		method = SWFInput_getUInt16_BE;
194 		rshift = 15;
195 		mask = 0xffff;
196 	}
197 	else
198 		return -1;
199 
200 	ichar = method(input);
201 	stream->width = ( (ichar << 1) & mask );
202 
203 	ichar = method(input);
204 	stream->width |= ( (ichar >> rshift) & mask );
205 	stream->height = ( (ichar << 1) & mask );
206 
207 	ichar = method(input);
208 	stream->height |= ( (ichar >> rshift) & mask );
209 
210 	return 0;
211 }
212 
setH263StreamDimension(SWFVideoStream stream,FLVTag * tag)213 static int setH263StreamDimension(SWFVideoStream stream, FLVTag *tag)
214 {
215 	SWFInput input;
216 	int ichar, flags;
217 
218 	input = FLVTag_getPayloadInput(tag);
219 	if(input == NULL)
220 		return -1;
221 
222 	/* skip:
223 	 * pictureStartCode UB[17] 2 byte ( 1 bit remaining )
224 	 * Version UB[5]
225 	 * Temporal Reference UB[8]
226 	 * Picture Size UB[3]
227 	 * */
228 	SWFInput_seek(input, 2, SEEK_CUR);
229 	ichar = SWFInput_getUInt16_BE(input);
230 
231 		/* 3-bit flag */
232 	flags = ((0x0003 & ichar) << 1);
233 
234 	ichar = SWFInput_getChar(input);
235 	flags |= ((0x80 & ichar) >> 7);
236 
237 	switch(flags)
238 	{
239 		case 6:
240 			stream->width = 160;
241 			stream->height = 120;
242 			return 0;
243 
244 		case 5:
245 			stream->width = 320;
246 			stream->height = 240;
247 			return 0;
248 
249 		case 4:
250 			stream->width = 128;
251 			stream->height = 96;
252 			return 0;
253 
254 		case 3:
255 			stream->width = 176;
256 			stream->height = 144;
257 			return 0;
258 
259 		case 2:
260 			stream->width = 352;
261 			stream->height = 288;
262 			return 0;
263 
264 		default:
265 			return setH263CustomDimension(stream, input, flags);
266 	}
267 }
268 
setScreenStreamDimension(SWFVideoStream stream,FLVTag * tag)269 static int setScreenStreamDimension(SWFVideoStream stream, FLVTag *tag)
270 {
271 	unsigned int ui16 = 0;
272 	int ic;
273 	SWFInput input;
274 
275 	input = FLVTag_getPayloadInput(tag);
276 	if(input == NULL)
277 		return -1;
278 
279 	/* special case: skip 1 byte */
280 	ic  = SWFInput_getChar(input);
281 
282 	ic = SWFInput_getChar(input);
283 	if(ic >= 0)
284 		ui16 = ic << 8;
285 
286 	ic = SWFInput_getChar(input);
287 	if(ic >= 0)
288 		ui16 |= ic;
289 
290 	stream->width = ui16 & 0x0fff;
291 
292 	ic = SWFInput_getChar(input);
293 	if(ic >= 0)
294 		ui16 = ic << 8;
295 
296 	ic = SWFInput_getChar(input);
297 	if(ic >= 0)
298 		ui16 |= ic;
299 
300 	stream->height = ui16 & 0x0fff;
301 
302 	return 0;
303 
304 }
305 
306 
writeSWFVideoStreamToMethod(SWFBlock block,SWFByteOutputMethod method,void * data)307 void writeSWFVideoStreamToMethod(SWFBlock block, SWFByteOutputMethod method, void *data)
308 {
309 	SWFVideoStream stream = (SWFVideoStream)block;
310 
311 	methodWriteUInt16(CHARACTERID(stream), method, data);
312 	methodWriteUInt16(stream->numFrames, method, data);
313 	methodWriteUInt16(stream->width, method, data);
314 	methodWriteUInt16(stream->height, method, data);
315 
316 	if(stream->embedded) {
317 		method(stream->smoothingFlag, data);
318 		method(stream->codecId, data);
319 	}
320 	/*
321 	 * in case of streaming video, flags and codec id must be 0x0
322 	 * if not 0 it craches player + browser
323 	 */
324 	else {
325 		method(0x0, data);
326 		method(0x0, data);
327 	}
328 };
329 
330 
completeSWFVideoStream(SWFBlock block)331 int completeSWFVideoStream(SWFBlock block) {
332 	(void) block;
333 	return VIDEOFRAME_BLOCK_SIZE;
334 }
335 
336 
destroySWFVideoStream(SWFVideoStream stream)337 void destroySWFVideoStream(SWFVideoStream stream) {
338 	destroySWFCharacter((SWFCharacter) stream);
339 }
340 
341 
setVP6Dimension(SWFVideoStream stream,FLVTag * tag)342 static int setVP6Dimension(SWFVideoStream stream, FLVTag *tag)
343 {
344 	SWFInput input;
345 	int ichar;
346 	int render_x, render_y;
347 
348 	input = FLVTag_getPayloadInput(tag);
349 	if(input == NULL)
350 		return -1;
351 
352 
353 	ichar = SWFInput_getChar(input);
354 	if(ichar == EOF)
355 		return -1;
356 
357 	if(ichar >> 7)
358 	{
359 		SWF_warn("setVP6Dimension: first frame is interframe\n");
360 		return -1;
361 	}
362 
363 	if(ichar & 1)
364 	{
365 		SWF_warn("setVP6Dimension: VP60!\n");
366 		return -1;
367 	}
368 
369 	ichar = SWFInput_getChar(input);
370 	ichar = SWFInput_getChar(input);
371 	render_x = SWFInput_getChar(input);
372 	render_y = SWFInput_getChar(input);
373 
374 	stream->width = render_x * 16;
375 	stream->height = render_y * 16;
376 	return 0;
377 }
378 
379 
setStreamProperties(SWFVideoStream stream)380 static int setStreamProperties(SWFVideoStream stream)
381 {
382 	int ret;
383 	FLVTag tag, *tag_p = NULL;
384 
385 	stream->numFrames = FLVStream_getNumFrames(stream->flv, FLV_VIDEOTAG);
386 
387 	while((ret = FLVStream_nextTag(stream->flv, &tag, tag_p)) == 0)
388 	{
389 		if(tag.tagType == FLV_VIDEOTAG)
390 			break;
391 
392 		tag_p = &tag;
393 	}
394 
395 	if(ret < 0)
396 		return -1;
397 
398 	stream->codecId = tag.hdr.video.codec;
399 	switch (stream->codecId) {
400 		case VIDEO_CODEC_H263:
401 			ret = setH263StreamDimension(stream, &tag);
402 			stream->smoothingFlag = VIDEO_SMOOTHING;
403 			break;
404 		case VIDEO_CODEC_SCREEN:
405 			ret = setScreenStreamDimension(stream, &tag);
406 			stream->smoothingFlag = 0;
407 			break;
408 		case VIDEO_CODEC_VP6:
409 			setVP6Dimension(stream, &tag);
410 			stream->smoothingFlag = VIDEO_SMOOTHING;
411 			ret = 0;
412 			break;
413 		case VIDEO_CODEC_VP6A:
414 		case VIDEO_CODEC_SCREEN2:
415 			SWF_warn("setStreamProperties: automatic dimension setting is not working with this codec yet!\n");
416 			ret = 0;
417 			break;
418 		default:
419 			SWF_warn("Unknown Codec %x\n", stream->codecId);
420 			ret = -1;
421 	}
422 	return ret;
423 }
424 
425 
onPlace(SWFDisplayItem item,SWFBlockList blocklist)426 static int onPlace(SWFDisplayItem item, SWFBlockList blocklist)
427 {
428 	SWFVideoStream stream = (SWFVideoStream)SWFDisplayItem_getCharacter(item);
429 	SWFBlock video = SWFVideoStream_getVideoFrame(stream);
430 	if(video == NULL)
431 		return 0;
432         SWFBlockList_addBlock(blocklist, video);
433 	stream->firstFrame = 0;
434 	return 1;
435 }
436 
onFrame(SWFDisplayItem item,SWFBlockList blocklist)437 static int onFrame(SWFDisplayItem item, SWFBlockList blocklist)
438 {
439 	SWFPlaceObject2Block placeVideo;
440 	SWFVideoStream stream;
441 	SWFBlock video = NULL;
442 
443 	/* if item is new -> onInit already inserted a frame */
444 	if(item->flags != 0)
445 		return 0;
446 
447 	stream = (SWFVideoStream)SWFDisplayItem_getCharacter(item);
448 	if(stream->mode == SWFVIDEOSTREAM_MODE_MANUAL &&
449 		stream->addFrame == 0)
450 		return 0;
451 
452 	if(stream->mode != SWFVIDEOSTREAM_MODE_MANUAL)
453 		stream->frame++;
454 
455 	if(stream->frame >= stream->framesLoaded)
456 	{
457 		video = SWFVideoStream_getVideoFrame(stream);
458 		if(video == NULL)
459 			return 0;
460 	}
461 
462 	placeVideo = newSWFPlaceObject2Block(item->depth);
463 	SWFPlaceObject2Block_setRatio(placeVideo, stream->frame);
464 	SWFPlaceObject2Block_setMove(placeVideo);
465 	SWFBlockList_addBlock(blocklist, (SWFBlock)placeVideo);
466 	if(video != NULL)
467 		SWFBlockList_addBlock(blocklist, video);
468 
469 	stream->addFrame = 0;
470 	return 2;
471 }
472 
473 
474 /**
475  * Seek within a video stream.
476  *
477  * This functions allows seeking in video stream. Semantics
478  * like SWFInput_seek();
479  *
480  * Works only with SWFVIDEOSTREAM_MODE_MANUAL!
481  * @return old video position (frame)
482  */
SWFVideoStream_seek(SWFVideoStream stream,int frame,int whence)483 int SWFVideoStream_seek(SWFVideoStream stream, int frame, int whence)
484 {
485 	int old, pos;
486 
487 	if(stream == NULL || stream->embedded == 0)
488 		return -1;
489 
490 	if(stream->mode != SWFVIDEOSTREAM_MODE_MANUAL)
491 		return -1;
492 
493 	old = stream->frame;
494 	switch(whence)
495 	{
496 	case SEEK_SET:
497 		if(frame < 0 || frame >= stream->numFrames)
498 			return -1;
499 		stream->frame = frame;
500 		break;
501 	case SEEK_END:
502 		if(frame < 0 || frame >= stream->numFrames)
503  			return -1;
504 		stream->frame = stream->numFrames - frame;
505 		break;
506 	case SEEK_CUR:
507 		pos = stream->frame + frame;
508 		if(pos < 0 || pos >= stream->numFrames)
509 			return -1;
510 		break;
511 	default:
512 		return -1;
513 	}
514 	stream->addFrame = 1;
515 	return old;
516 }
517 
518 /**
519  * Display next video frame
520  *
521  * Works only with embedded video streams.
522  *
523  * @return -1 if an error happend.
524  */
SWFVideoStream_nextFrame(SWFVideoStream stream)525 int SWFVideoStream_nextFrame(SWFVideoStream stream)
526 {
527 	if(stream == NULL || !stream->embedded)
528 		return -1;
529 
530 	if(stream->mode != SWFVIDEOSTREAM_MODE_MANUAL)
531 		return -1;
532 
533 	if(stream->addFrame == 1 || stream->firstFrame == 1)
534 		return 0;
535 
536 	stream->addFrame = 1;
537 	stream->frame++;
538 	return 0;
539 }
540 
541 /**
542  * switch video stream frame mode
543  * If the mode == SWFVIDEOSTREAM_MODE_AUTO (default) every swfmovie
544  * frame a video frame is added.
545  * In SWFVIDEOSTREAM_MODE_MANUAL mode, the user needs to call
546  * SWFVideoStream_nextFrame() to change the video's frame.
547  *
548  * Works only with embedded video streams.
549  *
550  * @return the previous mode or -1 if an invalid mode was passed.
551  */
SWFVideoStream_setFrameMode(SWFVideoStream stream,int mode)552 int SWFVideoStream_setFrameMode(SWFVideoStream stream, int mode)
553 {
554 	int oldmode;
555 	if(stream == NULL || !stream->embedded)
556 		return -1;
557 
558 	oldmode = stream->mode;
559 	switch(mode)
560 	{
561 	case SWFVIDEOSTREAM_MODE_AUTO:
562 		stream->mode = mode;
563 		return oldmode;
564 	case SWFVIDEOSTREAM_MODE_MANUAL:
565 		stream->mode = mode;
566 		return oldmode;
567 	default:
568 		SWF_warn("SWFVideoStream_setFrameMode: mode %i is unknown", mode);
569 		return -1;
570 	}
571 }
572 
573 /*
574  * create a new SWFVideoSteam object
575  * This function creates a new videostream object from a FLV-file.
576  * Takes a SWFInput object as argument.
577  * Be aware: You need to keep the SWFInput valid until the movie is generated via save() or output()!
578  */
579 SWFVideoStream
newSWFVideoStream_fromInput(SWFInput input)580 newSWFVideoStream_fromInput(SWFInput input) {
581 
582 	SWFBlock block;
583 	SWFVideoStream stream;
584 
585 	if(!input)
586 		return NULL;
587 
588 	stream = (SWFVideoStream)malloc(sizeof(struct SWFVideoStream_s));
589 	if(!stream)
590 		return NULL;
591 	block = (SWFBlock)stream;
592 
593 	SWFCharacterInit((SWFCharacter)stream);
594 	CHARACTERID(stream) = ++SWF_gNumCharacters;
595  	((SWFCharacter)stream)->onFrame = onFrame;
596  	((SWFCharacter)stream)->onPlace = onPlace;
597 	block->type = SWF_DEFINEVIDEOSTREAM;
598 	block->writeBlock = writeSWFVideoStreamToMethod;
599 	block->complete = completeSWFVideoStream;
600 	block->dtor = (destroySWFBlockMethod)destroySWFVideoStream;
601 
602 	stream->flv = FLVStream_fromInput(input);
603 	if(stream->flv == NULL)
604 	{
605 		free(stream);
606 		return NULL;
607 	}
608 	stream->lastTag = NULL;
609 	stream->lastFrame = 0;
610 	stream->frame = 0;
611 	stream->embedded = 1;
612 	stream->mode = SWFVIDEOSTREAM_MODE_AUTO;
613 	stream->addFrame = 0;
614 	stream->framesLoaded = 0;
615 	stream->firstFrame = 1;
616 	stream->width = VIDEO_DEF_WIDTH;
617 	stream->height = VIDEO_DEF_HEIGHT;
618 	if (setStreamProperties(stream) < 0)
619 	{
620 		free(stream);
621 		return NULL;
622 	}
623 	return stream;
624 }
625 
626 /*
627  * creates a new SWFVideoStream object
628  * This function creates an empty videostream object. This object can be adressed via
629  * ActionScript to connect and display a streamed video (progessive download / rtmp).
630  */
newSWFVideoStream()631 SWFVideoStream newSWFVideoStream() {
632 	SWFBlock block;
633 	SWFVideoStream stream = (SWFVideoStream)malloc(sizeof(struct SWFVideoStream_s));
634         if(!stream)
635                 return NULL;
636         block = (SWFBlock)stream;
637 
638 	SWFCharacterInit((SWFCharacter)stream);
639         CHARACTERID(stream) = ++SWF_gNumCharacters;
640 
641 	block->type = SWF_DEFINEVIDEOSTREAM;
642         block->writeBlock = writeSWFVideoStreamToMethod;
643         block->complete = completeSWFVideoStream;
644         block->dtor = (destroySWFBlockMethod)destroySWFVideoStream;
645 
646 	stream->flv = NULL;
647 	stream->lastTag = NULL;
648         stream->frame = 0;
649         stream->embedded = 0;
650 	stream->numFrames = -1;
651 
652 	stream->width = VIDEO_DEF_WIDTH;
653 	stream->height = VIDEO_DEF_HEIGHT;
654 	return stream;
655 }
656 
657 
658 /*
659  * create a new SWFVideoSteam object
660  * This function creates a new videostream object from a FLV-file.
661  * Takes a FILE * as argument.
662  * Be aware: You need to keep the FILE open until the movie is generated via save() or output()!
663  */
newSWFVideoStream_fromFile(FILE * f)664 SWFVideoStream newSWFVideoStream_fromFile(FILE *f /* FILE pointer to a FLV-file */) {
665 	return newSWFVideoStream_fromInput(newSWFInput_file(f));
666 }
667 
668 
669 /*
670  * sets video dimension
671  * This function set width and height for streamed videos
672  * Works only _streamed_ videos (progressive download or rtmp)
673  */
SWFVideoStream_setDimension(SWFVideoStream stream,int width,int height)674 void SWFVideoStream_setDimension(SWFVideoStream stream /* stream object */,
675 				int width, /* width in px */
676 				int height /* height in px */)
677 {
678 	if(!stream->embedded) {
679 		stream->width = width;
680 		stream->height = height;
681 	}
682 }
683 
684 
685 /*
686  * returns the number of video-frames
687  * This function returns the number of video-frames of a SWFVideoStream.
688  * Works only for embedded streams!
689  */
SWFVideoStream_getNumFrames(SWFVideoStream stream)690 int SWFVideoStream_getNumFrames(SWFVideoStream stream /* Embedded video stream */) {
691 	if(!stream)
692 		return -1;
693 	return stream->numFrames;
694 }
695 
696 /*
697  * returns 1 if embedded video stream has also an audio stream
698  * This functions test if the embedded FLV stream also has audio-data.
699  */
SWFVideoStream_hasAudio(SWFVideoStream stream)700 int SWFVideoStream_hasAudio(SWFVideoStream stream /* Embedded video stream */)
701 {
702 	if(stream != NULL && stream->flv != NULL && stream->flv->has_audio)
703 		return 1;
704 
705 	return 0;
706 }
707