1/******************************************************************************
2/
3/	File:			VideoIn.cpp
4/
5/	Description:	High-Level ATI Radeon Video Capture Interface.
6/
7/	Copyright 2001, Carlos Hasan
8/
9*******************************************************************************/
10
11#include <Debug.h>
12#include "VideoIn.h"
13#include <memory.h>
14
15static const theater_standard kStandard[] = {
16	C_THEATER_NTSC,
17	C_THEATER_NTSC_JAPAN,
18	C_THEATER_NTSC_443,
19	C_THEATER_PAL_M,
20	C_THEATER_PAL_BDGHI,
21	C_THEATER_PAL_N,
22	C_THEATER_PAL_60,
23	C_THEATER_PAL_NC,
24	C_THEATER_SECAM,
25//	C_THEATER_NTSC_RAW
26};
27
28static const theater_source kSource[] = {
29	C_THEATER_TUNER,
30	C_THEATER_COMPOSITE,
31	C_THEATER_SVIDEO
32};
33
34static const capture_buffer_mode kMode[] = {
35	C_RADEON_CAPTURE_FIELD_DOUBLE,
36	C_RADEON_CAPTURE_BOB_DOUBLE,
37	C_RADEON_CAPTURE_WEAVE_DOUBLE
38};
39
40static const struct {
41	struct {
42		int width, height;
43	} total;
44	struct {
45		int left, top;
46		int width, height;
47	} active;
48	struct {
49		int left, top;
50		int width, height;
51	} vbi;
52} kTiming[] = {
53	{ {  910, 525 }, { 112, 37, 755, 480 }, {  73, 13, 798, 20 } },	// NTSC-M
54	{ {  910, 525 }, { 112, 37, 755, 480 }, {  73, 13, 798, 24 } },	// NTSC-Japan
55	{ { 1042, 525 }, { 126, 37, 940, 480 }, {  73, 13, 798, 24 } }, // NTSC-443
56	{ {  910, 525 }, { 103, 37, 764, 480 }, {  73, 13, 798, 24 } }, // PAL-M
57
58	{ { 1135, 625 }, { 154, 35, 928, 576 }, { 132, 11, 924, 24 } },	// PAL-BDGHI
59	{ { 1135, 625 }, { 154, 35, 928, 576 }, { 132, 11, 924, 24 } }, // PAL-N
60	{ { 1125, 625 }, { 132, 37, 736, 480 }, { 100, 13, 770, 26 } }, // PAL-60
61	{ {  910, 625 }, { 125, 35, 957, 576 }, { 132, 11, 924, 24 } }, // PAL-NC
62	{ { 1135, 625 }, { 149, 35, 933, 576 }, { 132, 11, 924, 24 } },	// SECAM
63
64	{ {  910, 525 }, { 112, 37, 755, 480 }, {  73, 13, 798, 22 } }	// NTSC-Raw
65};
66
67class CTheater100;
68class CTheater200;
69
70CVideoIn::CVideoIn( const char *dev_name )
71	:	fRadeon( dev_name ),
72		fCapture(fRadeon),
73		fI2CPort(fRadeon),
74		fTheater(NULL),
75		fTuner(fI2CPort),
76		fSound(fI2CPort),
77		fBuffer0(0),
78		fBuffer1(0),
79		fBuffer0Handle(0),
80		fBuffer1Handle(0),
81		convert_buffer( NULL ),
82		fBufferLength(0),
83		fBufferBytesPerRow(0),
84		fBufferSequence(0),
85		fBufferPeriod(0),
86		started( false )
87{
88
89	Trace("CVideoIn::CVideoIn()");
90
91}
92
93CVideoIn::~CVideoIn()
94{
95	Trace("CVideoIn::~CVideoIn()");
96
97	FreeBuffers();
98}
99
100status_t CVideoIn::InitCheck()
101{
102	status_t res;
103	int device;
104
105	Trace("CVideoIn::InitCheck()");
106	if( (res = fRadeon.InitCheck()) != B_OK )
107		return res;
108
109	if( (res = fCapture.InitCheck()) != B_OK )
110		return res;
111
112	if( (res = fI2CPort.InitCheck()) != B_OK )
113		return res;
114
115	//debugger("init");
116	// detect type of theatre and initialise specific theater class
117	if ((device = fRadeon.FindVIPDevice( C_THEATER100_VIP_DEVICE_ID )) != -1)
118	{
119		Trace("CVideoIn::Found Rage Theater 100");
120		fTheater = new CTheater100(fRadeon, device);
121	}
122	else if ((device = fRadeon.FindVIPDevice( C_THEATER200_VIP_DEVICE_ID )) != -1)
123	{
124		Trace("CVideoIn::Found Rage Theater 200");
125		fTheater = new CTheater200(fRadeon, device);
126	}
127
128	if (fTheater)
129	{
130		res = fTheater->InitCheck();
131	}
132	else
133	{
134		res = B_ERROR;
135	}
136
137	return res;
138}
139
140int CVideoIn::Capabilities() const
141{
142	return  fTheater->Capabilities() +
143			(fTuner.InitCheck() == B_OK ? C_VIDEO_IN_HAS_TUNER : 0) +
144		   (fSound.InitCheck() == B_OK ? C_VIDEO_IN_HAS_SOUND : 0);
145}
146
147void CVideoIn::Start(video_in_source source, video_in_standard standard,
148					 video_in_capture_mode mode, int width, int height)
149{
150	char buffer[256];
151	sprintf(buffer, "CVideoIn::Start(%s, %d, %d)",
152		mode == C_VIDEO_IN_FIELD ? "C_VIDEO_IN_FIELD" :
153		mode ==	C_VIDEO_IN_BOB ? "C_VIDEO_IN_BOB" : "C_VIDEO_IN_WEAVE", width, height);
154	Trace(buffer);
155
156	if( started )
157		Stop();
158
159	switch (mode) {
160	case C_VIDEO_IN_FIELD:
161	case C_VIDEO_IN_BOB:
162		width = Clamp(width, 0, kTiming[standard].active.width);
163		height = Clamp(height, 0, kTiming[standard].active.height / 2);
164		break;
165	case C_VIDEO_IN_WEAVE:
166		width = Clamp(width, 0, kTiming[standard].active.width);
167		height = Clamp(height, 0, kTiming[standard].active.height);
168		break;
169	}
170
171	fBufferBytesPerRow = (2 * width + 15) & ~15;
172	fBufferLength = (fBufferBytesPerRow * height + 15) & ~15;
173
174	FreeBuffers();
175
176	// TBD:
177	// no error handling !!!!
178	fRadeon.AllocateGraphicsMemory(
179		mt_local,
180		mode == C_VIDEO_IN_BOB ? 4 * fBufferLength : 2 * fBufferLength,
181		&fBuffer0, &fBuffer0Handle );
182
183	fRadeon.AllocateGraphicsMemory(
184		mt_local,
185		mode == C_VIDEO_IN_BOB ? 2 * fBufferLength : 1 * fBufferLength,
186		&fBuffer1, &fBuffer1Handle );
187
188	convert_buffer = malloc( mode == C_VIDEO_IN_BOB ? 4 * fBufferLength : 2 * fBufferLength );
189
190	fBufferPeriod = 1000000000LL / getFrameRate( standard );
191
192	if( mode == C_VIDEO_IN_BOB )
193		fBufferPeriod >>= 1;
194
195	fTheater->SetStandard(kStandard[standard], kSource[source]);
196	fTheater->SetSize(width, (mode != C_VIDEO_IN_WEAVE ? 2 * height : height));
197
198	fCapture.SetBuffer(C_RADEON_CAPTURE_CCIR656, kMode[mode], fBuffer0, fBuffer1, fBufferLength, fBufferBytesPerRow >> 1);
199	fCapture.SetClip(0, kTiming[standard].vbi.height, width - 1, kTiming[standard].vbi.height + (mode != C_VIDEO_IN_WEAVE ? height : height >> 1) - 1);
200
201	fTheater->SetEnable(true, false);
202	if( fSound.InitCheck() == B_OK )
203		fSound.SetEnable(true);
204	fCapture.SetInterrupts(true);
205	fCapture.Start();
206}
207
208void CVideoIn::Stop()
209{
210	Trace("CVideoIn::Stop()");
211
212	if( !started )
213		return;
214
215	fCapture.Stop();
216	fCapture.SetInterrupts(false);
217	if( fSound.InitCheck() == B_OK )
218		fSound.SetEnable(false);
219	fTheater->SetEnable(false, false);
220
221	FreeBuffers();
222}
223
224void CVideoIn::FreeBuffers()
225{
226	if( fBuffer0Handle > 0 ) {
227		fRadeon.FreeGraphicsMemory( mt_local, fBuffer0Handle );
228		fBuffer0Handle = 0;
229	}
230
231	if( fBuffer1Handle > 0 ) {
232		fRadeon.FreeGraphicsMemory( mt_local, fBuffer1Handle );
233		fBuffer1Handle = 0;
234	}
235
236	if( convert_buffer != NULL ) {
237		free( convert_buffer );
238		convert_buffer = NULL;
239	}
240}
241
242void CVideoIn::SetBrightness(int brightness)
243{
244	Trace("CVideoIn::SetBrightness()");
245
246	fTheater->SetBrightness(brightness);
247}
248
249void CVideoIn::SetContrast(int contrast)
250{
251	Trace("CVideoIn::SetContrast()");
252
253	fTheater->SetContrast(contrast);
254}
255
256void CVideoIn::SetSaturation(int saturation)
257{
258	Trace("CVideoIn::SetSaturation()");
259
260	fTheater->SetSaturation(saturation);
261}
262
263void CVideoIn::SetHue(int hue)
264{
265	Trace("CVideoIn::SetHue()");
266
267	fTheater->SetHue(hue);
268}
269
270void CVideoIn::SetSharpness(int sharpness)
271{
272	Trace("CVideoIn::SetSharpness()");
273
274	fTheater->SetSharpness(sharpness);
275}
276
277void CVideoIn::SetFrequency(float frequency, float picture)
278{
279	Trace("CVideoIn::SetFrequency()");
280
281	if (fTuner.Type() != C_TUNER_NONE)
282		fTuner.SweepFrequency(frequency, picture);
283}
284
285float CVideoIn::FrequencyForChannel(int channel, video_in_standard standard)
286{
287	float frequency = 0;
288
289	Trace("CVideoIn::FrequencyForChannel()");
290
291	if (fTuner.Type() != C_TUNER_NONE) {
292		switch (standard) {
293		case C_VIDEO_IN_NTSC:
294		case C_VIDEO_IN_NTSC_RAW:
295			// NTSC Cable
296			if (channel >= 2 && channel <= 6) {
297				frequency = 55.25 + 6.00 * (channel - 2);
298			}
299			else if (channel >= 7 && channel <= 13) {
300				frequency = 175.25 + 6.00 * (channel - 7);
301			}
302			else if (channel >= 14 && channel <= 22) {
303				frequency = 121.25 + 6.00 * (channel - 14);
304			}
305			else if (channel >= 23 && channel <= 36) {
306				frequency = 217.25 + 6.00 * (channel - 23);
307			}
308			else if (channel >= 37 && channel <= 62) {
309				frequency = 301.25 + 6.00 * (channel - 37);
310			}
311			else if (channel >= 63 && channel <= 94) {
312				frequency = 457.25 + 6.00 * (channel - 63);
313			}
314			else if (channel >= 95 && channel <= 99) {
315				frequency = 91.25 + 6.00 * (channel - 95);
316			}
317			else if (channel >= 100 && channel <= 125) {
318				frequency = 649.25 + 6.00 * (channel - 100);
319			}
320			else {
321				frequency = 0;
322			}
323			break;
324		case C_VIDEO_IN_NTSC_JAPAN:
325		case C_VIDEO_IN_NTSC_443:
326		case C_VIDEO_IN_PAL_M:
327		case C_VIDEO_IN_PAL_BDGHI:
328		case C_VIDEO_IN_PAL_N:
329		case C_VIDEO_IN_PAL_60:
330		case C_VIDEO_IN_PAL_NC:
331		case C_VIDEO_IN_SECAM:
332			break;
333		}
334	}
335	return frequency;
336}
337
338bool CVideoIn::SetChannel(int channel, video_in_standard standard)
339{
340	Trace("CVideoIn::SetChannel()");
341
342	if (fTuner.Type() == C_TUNER_NONE)
343		return true;
344
345	const float frequency = FrequencyForChannel(channel, standard);
346
347	switch (standard) {
348	case C_VIDEO_IN_NTSC:
349	case C_VIDEO_IN_NTSC_RAW:
350		return fTuner.SweepFrequency(frequency, C_TUNER_NTSC_PICTURE_CARRIER / 100.0f);
351		break;
352	case C_VIDEO_IN_NTSC_JAPAN:
353	case C_VIDEO_IN_NTSC_443:
354	case C_VIDEO_IN_PAL_M:
355	case C_VIDEO_IN_PAL_BDGHI:
356	case C_VIDEO_IN_PAL_N:
357	case C_VIDEO_IN_PAL_60:
358	case C_VIDEO_IN_PAL_NC:
359	case C_VIDEO_IN_SECAM:
360		return fTuner.SweepFrequency(frequency, C_TUNER_PAL_PICTURE_CARRIER / 100.0f);
361	}
362	return false;
363}
364
365int CVideoIn::Capture(color_space colorSpace, void * bits, int bitsLength,
366					  int bytesPerRow, int * sequence, short * number, bigtime_t * when)
367{
368//	Trace("CVideoIn::Capture()");
369
370	int mask, counter;
371
372	if ((mask = fCapture.WaitInterrupts(sequence, when, fBufferPeriod)) == 0)
373		return 0;
374
375	*number = ((mask & (C_RADEON_CAPTURE_BUF0_INT | C_RADEON_CAPTURE_BUF1_INT)) != 0 ? 0 : 1);
376
377	int32 captured_buffer =
378		((mask & C_RADEON_CAPTURE_BUF0_INT) != 0 ? fBuffer0 :
379		 (mask & C_RADEON_CAPTURE_BUF1_INT) != 0 ? fBuffer1 :
380		 (mask & C_RADEON_CAPTURE_BUF0_EVEN_INT) != 0 ? fBuffer0 + fBufferLength :
381		 (mask & C_RADEON_CAPTURE_BUF1_EVEN_INT) != 0 ? fBuffer1 + fBufferLength : 0);
382
383	/*PRINT(("colorSpace:%x, bitsLength: %d, fBufferLength: %d, bytesPerRow: %d, fBufferBytesPerRow: %d\n",
384		colorSpace, bitsLength, fBufferLength, bytesPerRow, fBufferBytesPerRow ));*/
385
386	// always copy into main memory first, even if it must be converted by CPU -
387	// reading from graphics mem is incredibly slow
388	if (colorSpace == B_YCbCr422 && bitsLength <= fBufferLength && bytesPerRow == fBufferBytesPerRow) {
389		fRadeon.DMACopy( captured_buffer, bits, bitsLength, true, false );
390	}
391
392	else if (colorSpace == B_RGB32 && bitsLength <= 2 * fBufferLength && bytesPerRow == 2 * fBufferBytesPerRow) {
393		fRadeon.DMACopy( captured_buffer, convert_buffer, fBufferLength, true, false );
394
395#define RGB32
396#include "yuv_converter.h"
397#undef RGB32
398
399	}
400
401	else if (colorSpace == B_RGB16 && bitsLength <= fBufferLength && bytesPerRow == fBufferBytesPerRow) {
402		fRadeon.DMACopy( captured_buffer, convert_buffer, fBufferLength, true, false );
403
404#define RGB16
405#include "yuv_converter.h"
406#undef RGB16
407
408	}
409
410	else if (colorSpace == B_RGB15 && bitsLength <= fBufferLength && bytesPerRow == fBufferBytesPerRow) {
411		fRadeon.DMACopy( captured_buffer, convert_buffer, fBufferLength, true, false );
412
413#define RGB15
414#include "yuv_converter.h"
415#undef RGB15
416
417	}
418	else if (colorSpace == B_GRAY8 && 2 * bitsLength <= fBufferLength && 2 * bytesPerRow == fBufferBytesPerRow) {
419		fRadeon.DMACopy( captured_buffer, convert_buffer, fBufferLength, true, false );
420
421		static const unsigned short mask[] = {
422			0x00ff, 0x00ff, 0x00ff, 0x00ff };
423
424		asm volatile(
425		"1:\n"
426			"movq		0x00(%0),%%mm0\n"	// mm0 = Cr2' Y3 Cb2' Y2 Cr0' Y1 Cb0' Y0
427			"movq		0x08(%0),%%mm1\n"	// mm1 = Cr6' Y7 Cb6' Y6 Cr4' Y5 Cb4' Y4
428			"pand		%3,%%mm0\n"			// mm0 =  00  Y3  00  Y2  00  Y1  00  Y0
429			"pand		%3,%%mm1\n"			// mm1 =  00  Y7  00  Y6  00  Y5  00  Y4
430			"packuswb	%%mm1,%%mm0\n"		// mm0 =  Y7  Y6  Y5  Y4  Y3  Y2  Y1  Y0
431			"movq		%%mm0,(%1)\n"		// destination[0] = mm0
432			"addl		$0x10,%0\n"			// source += 16
433			"addl		$0x08,%1\n"			// destination += 8
434			"subl		$0x08,%2\n"
435			"jg			1b\n"
436			"emms\n"
437			:
438			: "r" (convert_buffer), "r" (bits), "r" (bitsLength), "m" (mask));
439	}
440	else if( colorSpace == B_NO_COLOR_SPACE ) {
441		// special case: only wait for image but don't copy it
442		;
443	}
444	else {
445		PRINT(("CVideoIn::Capture() - Bad buffer format\n"));
446	}
447
448	counter = *sequence - fBufferSequence;
449	fBufferSequence = *sequence;
450	return counter;
451}
452
453void CVideoIn::Trace(const char * message) const
454{
455	PRINT(("\x1b[0;30;34m%s\x1b[0;30;47m\n", message));
456}
457
458int32 CVideoIn::getFrameRate( video_in_standard standard )
459{
460	// TODO: I'm not really sure about these values
461	static const int32 frame_rate[] = {
462		29976, 29976, 29976, 25000, 25000, 25000, 29976, 25000, 25000, 29976
463	};
464
465	return frame_rate[standard];
466}
467
468void CVideoIn::getActiveRange( video_in_standard standard, CRadeonRect &rect )
469{
470	// in theory, we would ask fTheatre;
471	// in practice, values retrieved from there don't work;
472	// e.g. for PAL, according to Theatre, VBI height is 38, but you must set up the
473	// clipping unit to height 24! I have no clue what goes wrong there
474	rect.SetTo(
475		kTiming[standard].active.left,
476		kTiming[standard].active.top,
477		kTiming[standard].active.left + kTiming[standard].active.width - 1,
478		kTiming[standard].active.top + kTiming[standard].active.height - 1 );
479}
480