⛏️ index : haiku.git

/*
 * FireWire DV media addon for Haiku
 *
 * Copyright (c) 2008, JiSheng Zhang (jszhang3@mail.ustc.edu.cn)
 * Distributed under the terms of the MIT License.
 *
 */
/*
 * Copyright (C) 2003
 * 	Hidetoshi Shimokawa. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *
 *	This product includes software developed by Hidetoshi Shimokawa.
 *
 * 4. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "FireWireCard.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <OS.h>
#include <stdint.h>
#include <errno.h>

#include "glue.h"

#define TAG	(1<<6)
#define CHANNEL	63

/* for DV format */
#define FIX_FRAME	1

struct frac {
	int n,d;
};

struct frac frame_cycle[2]  = {
	{8000*100, 2997},	/* NTSC 8000 cycle / 29.97 Hz */
	{320, 1},		/* PAL  8000 cycle / 25 Hz */
};
int npackets[] = {
	250		/* NTSC */,
	300		/* PAL */
};
struct frac pad_rate[2]  = {
	{203, 2997},	/* = (8000 - 29.97 * 250)/(29.97 * 250) */
	{1, 15},	/* = (8000 - 25 * 300)/(25 * 300) */
};
const char *system_name[] = {"NTSC", "PAL"};
int frame_rate[] = {30, 25};

#define DV_PSIZE 512
#define DV_DSIZE 480
#define DV_NCHUNK 64

#define DV_NPACKET_R 256
#define DV_NPACKET_T 255
#define DV_TNBUF 100	/* XXX too large value causes block noise */
#define DV_NEMPTY 10	/* depends on DV_TNBUF */
#define DV_RBUFSIZE (DV_PSIZE * DV_NPACKET_R)
#define DV_MAXBLOCKS (300)
#define DV_CYCLE_FRAC 0xc00

/* for MPEGTS format */
typedef uint8_t mpeg_ts_pld[188];

struct mpeg_pldt {
#if BYTE_ORDER == BIG_ENDIAN
	uint32_t	:7,
				c_count:13,
				c_offset:12;
#else /* BYTE_ORDER != BIG_ENDIAN */
	uint32_t	c_offset:12,
				c_count:13,
				:7;
#endif /* BYTE_ORDER == BIG_ENDIAN */
	mpeg_ts_pld payload;
};


#define	MPEG_NCHUNK 8
#define	MPEG_PSIZE 596
#define	MPEG_NPACKET_R 4096
#define	MPEG_RBUFSIZE (MPEG_PSIZE * MPEG_NPACKET_R)


FireWireCard::FireWireCard(const char* path)
	: fInitStatus(B_OK),
	fDev(-1),
	fBuf(NULL),
	fPad(NULL)
{
	printf("FireWireCard opening %s\n", path);

	fDev = open(path, O_RDWR);
	if (fDev < 0) {
		printf("FireWireCard opening %s failed\n", path);
		fInitStatus = B_ERROR;
		return;
	}
}


FireWireCard::~FireWireCard()
{
	if (fDev > 0)
		close(fDev);
}


status_t
FireWireCard::InitCheck()
{
	return fInitStatus;
}


ssize_t
FireWireCard::Read(void** data)
{
	if (fFormat == FMT_MPEGTS)
		return MpegtsRead(data);
	else
		return DvRead(data);
}


status_t
FireWireCard::Extract(void* dest, void** src, ssize_t* sizeUsed)
{
	if (fFormat == FMT_MPEGTS)
		return MpegtsExtract(dest, src, sizeUsed);
	else
		return DvExtract(dest, src, sizeUsed);
}


void
FireWireCard::GetBufInfo(size_t* rbufsize, int* rcount)
{
	*rbufsize = fRbufSize;
	*rcount = fRcount;
}


status_t
FireWireCard::DetectRecvFn()
{
	char* buf;
	char ich = TAG | CHANNEL;
	struct fw_isochreq isoreq;
	struct fw_isobufreq bufreq;
	int len;
	u_int32_t* ptr;
	struct ciphdr* ciph;

	bufreq.rx.nchunk = 8;
	bufreq.rx.npacket = 16;
	bufreq.rx.psize = 1024;
	bufreq.tx.nchunk = 0;
	bufreq.tx.npacket = 0;
	bufreq.tx.psize = 0;

	if (ioctl(fDev, FW_SSTBUF, &bufreq) < 0)
		return errno;

	isoreq.ch = ich & 0x3f;
	isoreq.tag = (ich >> 6) & 3;

	if (ioctl(fDev, FW_SRSTREAM, &isoreq) < 0)
		return errno;

	buf = (char*)malloc(1024*16);
	len = read(fDev, buf, 1024*16);
	if (len < 0) {
		free(buf);
		return errno;
	}
	ptr = (u_int32_t*) buf;
	ciph = (struct ciphdr*)(ptr + 1);

	switch(ciph->fmt) {
		case CIP_FMT_DVCR:
			fprintf(stderr, "Detected DV format on input.\n");
			fFormat = FMT_DV;
			fBuf = malloc(DV_RBUFSIZE);
			fRbufSize = DV_PSIZE;
			fRcount = DV_NPACKET_R;
			fPad = malloc(DV_DSIZE*DV_MAXBLOCKS);
			memset(fPad, 0xff, DV_DSIZE*DV_MAXBLOCKS);
			break;
		case CIP_FMT_MPEG:
			fprintf(stderr, "Detected MPEG TS format on input.\n");
			fFormat = FMT_MPEGTS;
			fBuf = malloc(MPEG_RBUFSIZE);
			fRbufSize = MPEG_PSIZE;
			fRcount = MPEG_NPACKET_R;
			break;
		default:
			fprintf(stderr, "Unsupported format for receiving: fmt=0x%x", ciph->fmt);
	}
	free(buf);
	return B_OK;
}


ssize_t
FireWireCard::DvRead(void** buffer)
{
	struct fw_isochreq isoreq;
	struct fw_isobufreq bufreq;
	ssize_t len;
	char ich = TAG|CHANNEL;

	bufreq.rx.nchunk = DV_NCHUNK;
	bufreq.rx.npacket = DV_NPACKET_R;
	bufreq.rx.psize = DV_PSIZE;
	bufreq.tx.nchunk = 0;
	bufreq.tx.npacket = 0;
	bufreq.tx.psize = 0;
	if (ioctl(fDev, FW_SSTBUF, &bufreq) < 0)
		return errno;

	isoreq.ch = ich & 0x3f;
	isoreq.tag = (ich >> 6) & 3;

	if (ioctl(fDev, FW_SRSTREAM, &isoreq) < 0)
		return errno;

	len = read(fDev, fBuf, DV_RBUFSIZE);
	if (len < 0) {
		if (errno == EAGAIN) {
			fprintf(stderr, "(EAGAIN) - push 'Play'?\n");
			fflush(stderr);
		} else
			fprintf(stderr, "read failed");
		return errno;
	}
	*buffer = fBuf;
	return len;
}


status_t
FireWireCard::DvExtract(void* dest, void** src, ssize_t* sizeUsed)
{
	struct dvdbc* dv;
	struct ciphdr* ciph;
	struct fw_pkt* pkt;
	u_int32_t* ptr;
	int nblocks[] = {250 /* NTSC */, 300 /* PAL */};
	int npad, k, m, system = -1, nb;

	k = m = 0;
	ptr = (u_int32_t*) (*src);

	pkt = (struct fw_pkt*) ptr;
	ciph = (struct ciphdr*)(ptr + 1);	/* skip iso header */
	if (ciph->fmt != CIP_FMT_DVCR) {
		fprintf(stderr, "unknown format 0x%x", ciph->fmt);
		return B_ERROR;
	}
	ptr = (u_int32_t*) (ciph + 1);		/* skip cip header */
	if (pkt->mode.stream.len <= sizeof(struct ciphdr))
		/* no payload */
		return B_ERROR;
	for (dv = (struct dvdbc*)ptr;
			(char*)dv < (char *)(ptr + ciph->len);
			dv+=6) {

		if  (dv->sct == DV_SCT_HEADER && dv->dseq == 0) {
			if (system < 0) {
				system = ciph->fdf.dv.fs;
				fprintf(stderr, "%s\n", system_name[system]);
			}

			/* Fix DSF bit */
			if (system == 1 &&
				(dv->payload[0] & DV_DSF_12) == 0)
				dv->payload[0] |= DV_DSF_12;
			nb = nblocks[system];
			fprintf(stderr, "%d", k%10);
#if FIX_FRAME
			if (m > 0 && m != nb) {
				/* padding bad frame */
				npad = ((nb - m) % nb);
				if (npad < 0)
					npad += nb;
				fprintf(stderr, "(%d blocks padded)",
							npad);
				npad *= DV_DSIZE;
				memcpy(dest, fPad, npad);
				dest = (char*)dest + npad;
			}
#endif
			k++;
			if (k % frame_rate[system] == 0) {
				/* every second */
				fprintf(stderr, "\n");
			}
			fflush(stderr);
			m = 0;
		}
		if (k == 0) 
			continue;
		m++;
		memcpy(dest, dv, DV_DSIZE);
		dest = (char*)dest + DV_DSIZE;
	}
	ptr = (u_int32_t*)dv;
	*src = ptr;
	return B_OK;
}


ssize_t
FireWireCard::MpegtsRead(void** buffer)
{
	struct fw_isochreq isoreq;
	struct fw_isobufreq bufreq;
	char ich = TAG|CHANNEL;
	ssize_t len;


	bufreq.rx.nchunk = MPEG_NCHUNK;
	bufreq.rx.npacket = MPEG_NPACKET_R;
	bufreq.rx.psize = MPEG_PSIZE;
	bufreq.tx.nchunk = 0;
	bufreq.tx.npacket = 0;
	bufreq.tx.psize = 0;
	if (ioctl(fDev, FW_SSTBUF, &bufreq) < 0)
		return errno;

	isoreq.ch = ich & 0x3f;
	isoreq.tag = (ich >> 6) & 3;

	if (ioctl(fDev, FW_SRSTREAM, &isoreq) < 0)
		return errno;

	len = read(fDev, fBuf, MPEG_RBUFSIZE);
	if (len < 0) {
		if (errno == EAGAIN) {
			fprintf(stderr, "(EAGAIN) - push 'Play'?\n");
			fflush(stderr);
		} else
			fprintf(stderr, "read failed");
		return errno;
	}
	*buffer = fBuf;
	return len;
}
	

status_t
FireWireCard::MpegtsExtract(void* dest, void** src, ssize_t* sizeUsed)
{
	uint32_t* ptr;
	struct fw_pkt* pkt;
	struct ciphdr* ciph;
	struct mpeg_pldt* pld;
	int pkt_size, startwr;

	ptr = (uint32_t *)(*src);
	startwr = 0;

	pkt = (struct fw_pkt*) ptr;	
	/* there is no CRC in the 1394 header */
	ciph = (struct ciphdr*)(ptr + 1);	/* skip iso header */
	if (ciph->fmt != CIP_FMT_MPEG) {
		fprintf(stderr, "unknown format 0x%x", ciph->fmt);
		return B_ERROR;
	}
	if (ciph->fn != 3) {
		fprintf(stderr,	"unsupported MPEG TS stream, fn=%d (only" 
			"fn=3 is supported)", ciph->fn);
		return B_ERROR;
	}
	ptr = (uint32_t*) (ciph + 1);		/* skip cip header */

	if (pkt->mode.stream.len <= sizeof(struct ciphdr)) {
		/* no payload */
		/* tlen needs to be decremented before end of the loop */
		goto next;
	}

	/* This is a condition that needs to be satisfied to start
	   writing the data */
	if (ciph->dbc % (1<<ciph->fn) == 0)
		startwr = 1;
	/* Read out all the MPEG TS data blocks from current packet */
	for (pld = (struct mpeg_pldt *)ptr;
	    (intptr_t)pld < (intptr_t)((char*)ptr +
	    pkt->mode.stream.len - sizeof(struct ciphdr));
	    pld++) {
		if (startwr == 1) {
			memcpy(dest, pld->payload, sizeof(pld->payload));
			dest = (char*)dest + sizeof(pld->payload);
		}
	}

next:
	/* CRCs are removed from both header and trailer
	so that only 4 bytes of 1394 header remains */
	pkt_size = pkt->mode.stream.len + 4; 
	ptr = (uint32_t*)((intptr_t)pkt + pkt_size);
	*src = ptr;
	return B_OK;
}