Re: Streaming Audio to CAPI



Hi,

Kinfe Tadesse wrote:

I am using C++ compiler. But I use C standard functions here and there
when I feel they are easier.

If it's C, you're invading the implementation namespace here by using
an identifier starting with an underscore. Don't do that, as it leads
to undefined behaviour. From a C point of view _DATA_B3_R might very
well expand to system("format c: /y") or something similar.


What?? I didn't know this. "format C:/y"? This is the most catastrophic
thing I can imagine. If you really meant it, it is something I want to
share with many other people using C.

Hmmm, I don't know if that holds true with C++ -- I haven't even read
the C++ ISO standard, while I could probably recite the ISO C
standard. There are some pitfalls indeed -- identifiers starting with
underscores are prohibited for users, as are identifiers starting with
"str" after including <string.h> and so on.

While of course in reality _DATA_B3_R won't be defined by your
C-compiler to "format c: /y" or similar, it legally could and there'd
be no one you could sue. As soon as you leave the realms of defined
behaviour everything becomes undefined by definition. Heh. Pun
intended.

[lots of snippage]

I'd recommend writing a bunch of encoder/decoder functions which
write to/read from a buffer (an array of bytes, that is). Get started by
implementing something like write_byte(), write_word(), write_length()
and so on. You could for example even have a begin_struct() and
end_struct() pair of functions which could be called nestedly.
end_struct() could then prefix everything that has been written to the
buffer since the last call to begin_struct() with a length prefix
coded according to CAPI as 1 or 3 bytes.

You see where we are going here?

There's plenty of both commercial and non-commercial CAPI software
written in that kind of naive way you're trying. Sooner or later all
such software does break.

I found it hard to follow because your assumption that I am using C wasn't
right

Look, you've defined a CAPI message similar to this:

struct msg {
WORD total_length;
WORD appl_id;

/* And so on. */
};

This generates a structural data type, but doesn't guarantee a given
binary representation. You might expect that in memory your structure
looks like this:

|........|........|........|........|
| total_length | appl_id |
|........|........|........|........|

But that certainly doesn't need to be the case. Your compiler could
(yeah, in reality it most certainly will not, but still) insert half a
megabyte of padding bytes between total_length and appl_id -- which
would totally break the CAPI-esqueness of your struct definition while
being perfectly legitimate.

Look at this code. (Most probably doesn't even compile and is full of
mistakes...)


/* -------------------------------------------------------------- */
struct reader {
unsigned char *blk;
unsigned long pos;
unsigned long size;
};

void reader_init( struct reader *rdr,
unsigned char *blk,
unsigned char *size ) {
rdr->blk = blk;
rdr->pos = 0;
rdr->size = size;
}

/* Retrieves number of bytes that are remaining in the reader. */
void reader_rem( struct reader *rdr ) {
return rdr->size - rdr->pos;
}

/* Skips a number of bytes. */
void reader_skip( struct reader *rdr, unsigned long skip ) {
if ( rdr->pos + skip > rdr->size )
rdr->pos = rdr->size;
else
rdr->pos += skip;
}

/* Reads a byte. */
unsigned char reader_readb( struct reader *rdr, unsigned char dflt ) {
if( rdr->pos >= rdr->size )
return dflt;

return rdr->blk[rdr->pos++];
}

/* Reads a word. */
unsigned short reader_readw( struct reader *rdr, unsigned short dflt ) {
if( rdr->pos + 1 >= rdr->size ) {
rdr->pos = rdr->size;
return dflt;
}

return reader_readb( rdr, 0 )
| reader_readb( rdr, 0 ) << 8;
}

/* Reads a dword. */
unsigned long reader_readdw( struct reader *rdr, unsigned long dflt ) {
if( rdr->pos + 3 >= rdr->size ) {
rdr->pos = rdr->size;
return dflt;
}

return reader_readb( rdr, 0 )
| reader_readb( rdr, 0 ) << 8;
| reader_readb( rdr, 0 ) << 16;
| reader_readb( rdr, 0 ) << 24;
}

/* Reads a CAPI length. */
unsigned short reader_readlen( struct reader *rdr ) {
unsigned char l;
l = reader_readb( rdr, 0 );
if( l != 0xff )
return l;
return reader_readw( rdr, 0 );
}

/* Initializes a reader such that the contents of a CAPI struct can be
* read from it. The source-readers cursor position is moved behind
* the struct.
*/
void reader_readstruct( struct reader *dest, struct reader *src ) {
unsigned short l;

l = reader_readlen( rdr );
dest->blk = src->blk;
dest->pos = src->pos;
dest->size = src->size;
if( dest->pos + l < dest->size )
dest->size = dest->pos + l;
reader_skip( src, l );
}
/* -------------------------------------------------------------- */


This code would work everywhere, from Windows on x86 to MacOS on PPC,
while your struct hack would not. You could simply decode a CAPI
message by doing:

void decode_msg( unsigned char *msg, unsigned long len ) {
struct reader rdr;
unsigned short total_len;
unsigned short appl_id;
unsigned char command;
unsigned char subcommand;
unsigned short msgnum;

reader_init( &rdr, msg, len );

total_len = reader_readw( &rdr, 0 );
if( total_len != len ) {
/* Something's broken! */
return;
}

appl_id = reader_readw( &rdr, 0 );
command = reader_readb( &rdr, 0 );
subcommand = reader_readb( &rdr, 0 );
msgnum = reader_readw( &rdr, 0 );

/* Here you could do your dispatch, based on command and
* subcommand.
*/
}

You can probably implement the counterpart (a 'writer', that is)
easily. For the remainder of this post I'm assuming you've done so :-)

You're quite far away from streaming audio, actually.

Yes I know. Can you suggest what I have to do to do so briefly ? From your
explantions so far, it seems that this task is getting much more
complicated than I ever imagined.

Ok, back to zero. You probably know that digital audio data consists
of "samples." The analog audio input is sampled N times per second and
the sampled data-point is recorded.

An audio CD does record a sample 44100 times per second and for each
sample has a resolution of 16 bit. The ISDN does it quite a bit
differently, only 8000 samples per second are transmitted, with 8 bit
per sample. Because 8 bit-sampled audio would sound quite bad, some
encoding trickery is performed, which allows 12 (or was it 13?) bits
of sampled data be encoded in 8 bits. This is the G.711 codec, which
comes in two variants -- G.711 A-Law and G.711 mu-Law.

To cut a long story short, in Europe we're using A-Law and in America
mainly mu-Law gets used.

So what you're going to need first is a .wav-file containing A-Law
encoded data -- you can make one easily with any of the freely
available audio-editors such as Audacity or so. Make sure you save it
as Mono, G.711 A-Law encoded audio with 8000 samples per second.

Since CAPI doesn't know anything about .wav-Files, you'll need to
strip out all .wav-specific data from that file. http://www.wotsit.org
knows about the Wave File format and what needs to be cut out. In the
end you need to have a binary file containing A-Law samples only,
nothing else. Normally CAPI applications do this on-the-fly, but I'd
recommend some smaller steps first.

If you find it too difficult to produce such a raw-file, I could send
you one.

This file is what's going to be streamed to to the remote side. With
CAPI_REGISTER you set a parameter whose name escapes me right now, I
think it's "block size" or similar. This is directly controls the
maximum size the chunks you send down to CAPI via DATA_B3_REQ may
have.

What you need to do is:

- establish a call (CONNECT_REQ, CONNECT_CONF, CONNECT_ACTIVE_IND,
CONNECT_ACTIVE_RESP)
- establish a layer-3 connection ( CONNECT_B3_REQ, CONNECT_B3_CONF,
CONNECT_B3_ACTIVE_IND, CONNECT_B3_ACTIVE_RESP )
- stream audio data to the remote side, block by block.

With CONNECT_REQ you have to specifiy the protocol suites you call is
going to use -- as you just want to play audio data thats
"Transparent, Transparent, Transparent" -- i.e. all three conceptual
layers pass through the data you send unmodified.

So you read a block of the given size from your raw wave-file, store
it somewhere in memory and send a DATA_B3_REQ down to CAPI. Your CAPI
implementation then will start playing back those data to the remote
side and when it's done it will post a DATA_B3_CONF. Obviously, you
can't touch the block of data in the meantime as CAPI might be
accessing it all the way through.

Now, when you receive a DATA_B3_CONF and only then send the next
DATA_B3_REQ you'll have noticeable gaps in audio output. The reason is
quite logical: you give CAPI a block of data, CAPI plays that block,
notifies you and you send down the next block -- there's some inherent
delay in that mechanism.

So what you need to do is send a number of blocks (CAPI supports up to
seven) down to CAPI. CAPI will play the first one, confirm it and
start playing the next one right away. You then have enough time to
send down even another block without causing gaps -- as CAPI still
has 6 blocks which it can play before your next block is required.

(Most probably uncompileable) pseudogode again:

#define BLKSIZE 512
#define CHNKCOUNT 7

struct chunk {
unsigned long size;
unsigned char blk[ BLKSIZE ];
};

struct chunk chunks[CHNKCOUNT];

FILE *input;

void start_playback() {
struct writer wr;
int i;

/* Fill internal buffer from wave-file */
for( i=0; i< CHNKCOUNT; i++ )
chunks[i].size = fread(
chunks[i].blk,
1,
BLKSIZE,
input );

/* Send all buffers containing actual data down to CAPI. */
for( i=0; i<CHNKCOUNT && chunks[i].size ; i++ ) {

/* TODO: construct message header. */
writer_writedw( wr, NCCI );

/* Write a pointer to the chunk of audio data into the
* message. This is positively UGLY!
*/
writer_writedw( wr, (unsigned long)chunks[i].blk );
writer_writew( wr, chunks[i].size );
writer_writew( wr, i );
writer_writew( wr, 0 ); /* No flags */

CAPI_PUT_MESSAGE( APPL_ID, BUFFER );
}
}

So to start playback, we read a number of blocks into memory. For each
block read we then generate a DATA_B3_REQ message. A pointer to the
block is then written into the message (Sigh. Something that's not
allowed by any C or C++ standard in this world -- converting pointers
to integers and back. That's total idiocy on part of the CAPI makers.
There are systems where pointers aren't roundtrip-convertible to and
from integers... Say goodbye to the remote chance of CAPI on AS/400 or
so.)

As the data handle we just set our "block number". Here we go by
posting 7 blocks a 512 bytes -- about half a second of data.

Now whenenever a DATA_B3_CONF comes up, you determine from it the data
handle and go like this:

void handle_conf( unsigned long handle ) {
struct writer wr;
int i;

/* Re-fill chunk */
chunks[handle].size = fread(
chunks[handle].blk,
1,
BLKSIZE,
input );

/* TODO: handle end of source file... */
/* TODO: construct message header. */
writer_writedw( wr, NCCI );

writer_writedw( wr, (unsigned long)chunks[handle].blk );
writer_writew( wr, chunks[handle].size );
writer_writew( wr, handle );
writer_writew( wr, 0 ); /* No flags */

CAPI_PUT_MESSAGE( APPL_ID, BUFFER );
}

Now you have a gapless self-driven chain of DATA_B3_REQs going with
CAPI -- kind of a "bucket brigade".

If you try this out you should notice one more thing: your audio will
sound absolutely horrible. All audio-data needs to be sent down to
CAPI bit-flipped -- that is, every byte you send down has to be
mirrored. That is, 0x01 becomes 0x80 and so on. Basically
[abcd|efgh] --> [hgfe|dcba].

Something like this could help:

void flip( unsigned char data, unsigned long len ) {
unsigned long i;
static const unsigned char flipt[256] = {
0x00, 0x80,
/* Lots of elements which you can fill out. */
..., 0xFF
};
for( i=0; i<len; i++ )
data[i] = flipt[data[i]];
}

Which would make that:

/* Re-fill chunk */
chunks[handle].size = fread(
chunks[handle].blk,
1,
BLKSIZE,
input );
flip( chunks[handle].blk, chunks[handle].size );


Hope this might help a little.

Best regards,

Danilo
.



Relevant Pages

  • [PATCH, RFC] CAPI crash / race condition
    ... Unable to handle kernel NULL pointer dereference at virtual address 00000010 ... netconsole capi capifs 3c59x mii fcdsl kernelcapi uhci_hcd usbcore ide_cd cdrom ... struct list_head. ...
    (Linux-Kernel)
  • Re: Streaming Audio to CAPI
    ... Neither does your code compile (partly because ... I am novice to CAPI and the purpose is ... C struct and now pass it to CAPI_PUT_MESSAGE. ... throwing away all other CAPI messages you receive. ...
    (comp.dcom.isdn.capi)
  • Re: Streaming Audio to CAPI
    ... C struct and now pass it to CAPI_PUT_MESSAGE. ... CAPI defines a message encoding as a sequence of bytes. ... I'd recommend taking a CAPI application which can generate a ... throwing away all other CAPI messages you receive. ...
    (comp.dcom.isdn.capi)