/* $NetBSD: audio.c,v 1.155 2002/03/23 17:17:10 kent Exp $ */ |
追従履歴:
2002/03/21: audio.c,v 1.154
2002/03/24: audio.c,v 1.155
int audio_read(struct audio_softc *sc, struct uio *uio, int ioflag) { struct audio_ringbuffer *cb = &sc->sc_rr; u_char *outp; int error, s, used, cc, n; const struct audio_params *params; int hw_bits_per_sample; if (cb->mmapped) return EINVAL; DPRINTFN(1,("audio_read: cc=%lu mode=%d\n", (unsigned long)uio->uio_resid, sc->sc_mode)); params = &sc->sc_rparams; switch (params->hw_encoding) { case AUDIO_ENCODING_SLINEAR_LE: case AUDIO_ENCODING_SLINEAR_BE: case AUDIO_ENCODING_ULINEAR_LE: case AUDIO_ENCODING_ULINEAR_BE: hw_bits_per_sample = params->hw_channels * params->precision * params->factor; break; default: hw_bits_per_sample = 8 * params->factor / params->factor_denom; } error = 0; /* * If hardware is half-duplex and currently playing, return * silence blocks based on the number of blocks we have output. * half duplex のハードウェアで、現在再生中なら * 無音ブロックを返す(?)。 */ if (!sc->sc_full_duplex && (sc->sc_mode & AUMODE_PLAY)) { while (uio->uio_resid > 0 && !error) { s = splaudio(); for(;;) { cc = sc->sc_pr.stamp - sc->sc_wstamp; if (cc > 0) break; DPRINTF(("audio_read: stamp=%lu, wstamp=%lu\n", sc->sc_pr.stamp, sc->sc_wstamp)); if (ioflag & IO_NDELAY) { splx(s); return (EWOULDBLOCK); } error = audio_sleep(&sc->sc_rchan, "aud_hr"); if (sc->sc_dying) error = EIO; if (error) { splx(s); return (error); } } splx(s); if (uio->uio_resid < cc) cc = uio->uio_resid; DPRINTFN(1,("audio_read: reading in write mode, " "cc=%d\n", cc)); error = audio_silence_copyout(sc, cc, uio); sc->sc_wstamp += cc; } return (error); } /* * uio_resid がなくなるまでループする。 * ユーザランドから指定された残り読み込み量。 */ while (uio->uio_resid > 0 && !error) { /* * ユーザランドに渡すデータはリングバッファ cb から直接渡す * のではなく、一旦 rconvbuffer に移ったものの中から渡される。 * そのため rconvbuffer が空っぽなら if 文の中に入って * rconvbuffer を埋める。rconvbuffer_end と _begin はポインタ * ではなく rconvbuffer からの位置を示す整数値。従って * _end - _begin == 0 なら rconvbuffer が空であることを示して * いる。_end - _begin が負になることはありえないはずだが * 念のためかしら? DIAGNOSTIC で printf 入れたほうがよくない? * #if DIAGNOSTIC * if (rconvbuffer_end - rconvbuffer_begin < 0) * printf("hoge"); * #endif */ if (sc->sc_rconvbuffer_end - sc->sc_rconvbuffer_begin <= 0) { s = splaudio(); /* * リングバッファ cb の使用量(水位)が 1サンプリング分 * もなければ、それ以上たまるまで while() の中で sleep * とかする。 */ while (cb->used * 8 < hw_bits_per_sample) { if (!sc->sc_rbus) { error = audiostartr(sc); if (error) { splx(s); return (error); } } if (ioflag & IO_NDELAY) { splx(s); return (EWOULDBLOCK); } DPRINTFN(1, ("audio_read: sleep used=%d\n", cb->used)); error = audio_sleep(&sc->sc_rchan, "aud_rd"); if (sc->sc_dying) error = EIO; if (error) { splx(s); return error; } } /* * Move data in the ring buffer to sc_rconvbuffer as * possible with/without rate conversion. * 周波数変換する/しないに関わらずリングバッファ(cb) * の中のデータを rconvbuffer に移動する。 */ used = cb->used; outp = cb->outp; cb->copying = 1; splx(s); /* * cc はこれから移動しようとするバイト数。 * とりあえず今リングバッファにあるデータの * 下限水位を越えている分を対象にする。 */ cc = used - cb->usedlow; /* maximum to read */ /* * rconvbuffer より大きくても読み込めないので * rconvbuffer_size で律速する。 */ if (cc > sc->sc_rconvbuffer_size) cc = sc->sc_rconvbuffer_size; /* * factor_denom > 1 の場合、後の sw_code を通した後で * データサイズが factor_denom 倍になるので、 * その分を考慮して読み込むサイズを減らしておく。 */ n = cc * params->factor / params->factor_denom; if (n < cc) cc = n; /* よく分からんけど端数を切り捨てて align する */ /* cc must be aligned by the sample size */ cc = (cc * 8 / hw_bits_per_sample) * hw_bits_per_sample / 8; #ifdef DIAGNOSTIC if (cc == 0) printf("audio_read: cc=0 hw_bits_per_sample=%d\n", hw_bits_per_sample); #endif /* * The format of data in the ring buffer is * [hw_sample_rate, hw_encoding, hw_precision, hw_channels] * この時点でリングバッファ(cb)の中のデータは * o ハードウェア独自のサンプリングレート * o ハードウェア独自のエンコード * o ハードウェア独自の precision * o ハードウェア独自のチャンネル数 */ #if NAURATECONV > 0 /* * NAURATECONV が指定されているドライバの場合の処理。 * 周波数変換が必要ないドライバの場合でも auconv_record() * が処理してくれるらしい。 * * auconv_record() は周波数とチャンネル数を変換するもの。 * outp から cc バイトのデータを変換して rconvbuffer に * 格納する。出力されたデータのサイズは rconvbuffer_end * に入っている。 */ sc->sc_rconvbuffer_end = auconv_record(&sc->sc_rconv, params, sc->sc_rconvbuffer, outp, cc); #else /* * NAURATECONV が指定されていない場合はこちら。 * auconv_record() は結果としてリングバッファ(cb)から * rconvbuffer へのコピーを行なっていることになるので、 * こちらでも memcpy を使って rconvbuffer にコピーし * 整合性をとる。 */ n = cb->end - outp; if (cc <= n) { memcpy(sc->sc_rconvbuffer, outp, cc); } else { memcpy(sc->sc_rconvbuffer, outp, n); memcpy(sc->sc_rconvbuffer + n, cb->start, cc - n); } sc->sc_rconvbuffer_end = cc; #endif /* !NAURATECONV */ /* * The format of data in sc_rconvbuffer is * [sample_rate, hw_encoding, hw_precision, channels] * この時点で rconvbuffer から始まるデータは * o ユーザランドが指定したサンプリングレート * o ハードウェア独自のエンコード * o ハードウェア独自の precision * o ユーザランドが指定したチャンネル数 */ /* * リングバッファ(cb)のポインタを書き戻す。 * これで cb にはもう用はない。 */ used -= cc; outp += cc; if (outp >= cb->end) outp -= cb->end - cb->start; s = splaudio(); cb->outp = outp; cb->used = used; cb->copying = 0; splx(s); /* * sw_code が指定されている場合 sw_code でエンコードと * precision の変換を行なう。 */ if (params->sw_code) { /* * 録音時の sw_code に渡す cc は、sw_code で * 変換した後のデータサイズという変態仕様らしい。 * とりあえず rconvbuffer の中身全部にする。 */ cc = sc->sc_rconvbuffer_end; #ifdef DIAGNOSTIC if (cc % params->factor != 0) printf("audio_read: cc is not aligned" ": cc=%d factor=%d\n", cc, params->factor); #endif /* * factor をかけて sw_code を通した後のデータ * サイズを事前に用意する。 * factor の定義は再生時の sw_code の通過前後の * データサイズの比率なので、録音時はふつう逆数 * になる。 */ cc = cc * params->factor_denom / params->factor; #ifdef DIAGNOSTIC if (cc == 0) printf("audio_read: cc=0 " "factor=%d/%d\n", params->factor, params->factor_denom); #endif /* * sw_code を実行して rconvbuffer からのデータを * 変換する。変換後のデータは rconvbuffer に上書き * されるというこちらも変態仕様。 * 変換後のサイズはすでに cc で求まっているので * rconvbuffer_end には cc を代入するだけ。 */ params->sw_code(sc->hw_hdl, sc->sc_rconvbuffer, cc); sc->sc_rconvbuffer_end = cc; } /* * rconvbuffer は先頭から使用しているので _begin = 0。 */ sc->sc_rconvbuffer_begin = 0; /* * The format of data in sc_rconvbuffer is * [sample_rate, encoding, precision, channels] * この時点で rconvbuffer のデータは * o ユーザランドから指定されたサンプリングレート * o ユーザランドから指定されたエンコード * o ユーザランドから指定された precision * o ユーザランドから指定されたチャンネル数 */ /* ここが if 文の終り */ } /* * ここでめでたく、rconvbuffer に必ずデータが入っている状態 * になったのでユーザランドにデータを渡す。 * cc はユーザランドに渡すバイト数。とりあえず目一杯渡すことに * する。 */ cc = sc->sc_rconvbuffer_end - sc->sc_rconvbuffer_begin; /* * cc が uio_resid よりも大きければ、返しすぎ(?)ないように * uio_resid で律速する。 */ if (uio->uio_resid < cc) cc = uio->uio_resid; /* and no more than we want */ DPRINTFN(0,("audio_read: buffer=%p[%d] (~ %d), cc=%d\n", sc->sc_rconvbuffer, sc->sc_rconvbuffer_begin, sc->sc_rconvbuffer_end, cc)); /* * uiomove() はユーザランドにデータを返して uio_resid を * 更新するので、uiomove() 前後の uio_resid を比較すると * 実際に返したバイト数が分かる。それが cc。 */ n = uio->uio_resid; error = uiomove(sc->sc_rconvbuffer + sc->sc_rconvbuffer_begin, cc, uio); cc = n - uio->uio_resid; /* number of bytes actually moved */ /* * 実際に返した分だけ rconvbuffer_begin を進める。 * rconvbuffer に溜っているやつを全部ユーザランドに返したら * _begin == _end となるため、バッファは再び rconvbuffer の * 先頭から使用されるという仕組み。 */ sc->sc_rconvbuffer_begin += cc; } return (error); } |
void audio_calc_blksize(struct audio_softc *sc, int mode) { struct audio_hw_if *hw = sc->hw_if; struct audio_params *parm; struct audio_ringbuffer *rb; int bs; if (sc->sc_blkset) return; if (mode == AUMODE_PLAY) { parm = &sc->sc_pparams; rb = &sc->sc_pr; } else { parm = &sc->sc_rparams; rb = &sc->sc_rr; } bs = parm->hw_sample_rate * audio_blk_ms / 1000 * parm->hw_channels * parm->precision / NBBY * parm->factor; ROUNDSIZE(bs); if (hw->round_blocksize) bs = hw->round_blocksize(sc->hw_hdl, bs); /* * The blocksize should never be 0, but a faulty * driver might set it wrong. Just use something. */ if (bs <= 0) bs = 512; rb->blksize = bs; DPRINTF(("audio_calc_blksize: %s blksize=%d\n", mode == AUMODE_PLAY ? "play" : "record", bs)); } |
int audio_write(struct audio_softc *sc, struct uio *uio, int ioflag) { struct audio_ringbuffer *cb = &sc->sc_pr; u_char *inp, *einp; int saveerror, error, s, n, cc, used; struct audio_params *params; int samples, hw_bits_per_sample, user_bits_per_sample; int input_remain, space; DPRINTFN(2,("audio_write: sc=%p count=%lu used=%d(hi=%d)\n", sc, (unsigned long)uio->uio_resid, sc->sc_pr.used, sc->sc_pr.usedhigh)); if (cb->mmapped) return EINVAL; if (uio->uio_resid == 0) { sc->sc_eof++; return 0; } /* * If half-duplex and currently recording, throw away data. * half duplex で現在再生中ならデータ捨てる。 */ if (!sc->sc_full_duplex && (sc->sc_mode & AUMODE_RECORD)) { uio->uio_offset += uio->uio_resid; uio->uio_resid = 0; DPRINTF(("audio_write: half-dpx read busy\n")); return (0); } if (!(sc->sc_mode & AUMODE_PLAY_ALL) && sc->sc_playdrop > 0) { n = min(sc->sc_playdrop, uio->uio_resid); DPRINTF(("audio_write: playdrop %d\n", n)); uio->uio_offset += n; uio->uio_resid -= n; sc->sc_playdrop -= n; if (uio->uio_resid == 0) return 0; } params = &sc->sc_pparams; DPRINTFN(1, ("audio_write: sr=%ld, enc=%d, prec=%d, chan=%d, sw=%p, " "fact=%d\n", sc->sc_pparams.sample_rate, sc->sc_pparams.encoding, sc->sc_pparams.precision, sc->sc_pparams.channels, sc->sc_pparams.sw_code, sc->sc_pparams.factor)); /* * For some encodings, handle data in sample unit. */ switch (params->hw_encoding) { case AUDIO_ENCODING_SLINEAR_LE: case AUDIO_ENCODING_SLINEAR_BE: case AUDIO_ENCODING_ULINEAR_LE: case AUDIO_ENCODING_ULINEAR_BE: hw_bits_per_sample = params->hw_channels * params->precision * params->factor; user_bits_per_sample = params->channels * params->precision; break; default: hw_bits_per_sample = 8 * params->factor / params->factor_denom; user_bits_per_sample = 8; } #ifdef DIAGNOSTIC if (hw_bits_per_sample > MAX_SAMPLE_SIZE * 8) { printf("audio_write(): Invalid sample size: cur=%d max=%d\n", hw_bits_per_sample / 8, MAX_SAMPLE_SIZE); } #endif /* * 何のスペース計算してんの? */ space = ((params->hw_sample_rate / params->sample_rate) + 1) * hw_bits_per_sample / 8; error = 0; /* * データがある間ループする? * uio_resid はユーザランドから渡されたデータの残り量。 * sc_input_fagment_length は前回の書き込み時のあまり。 * つまり input_remain は過去と今回を含めユーザランドから渡されて * いるデータのうち未処理の(= ハードウェアに渡していない)バイト数。 */ while ((input_remain = uio->uio_resid + sc->sc_input_fragment_length) > 0 && !error) { s = splaudio(); /* * バッファ内の残りデータ量がユーザレベルでの1サンプル分も * ないなら、ユーザランドから読み込む。 */ if (input_remain < user_bits_per_sample / 8) { n = uio->uio_resid; DPRINTF(("audio_write: fragment uiomove length=%d\n", n)); error = uiomove(sc->sc_input_fragment + sc->sc_input_fragment_length, n, uio); /* * XXX: なんで n 足してるんだろう。 * sc->sc_input_fragment_length += n - uio->uio_resid; * じゃなくていいんだっけ? */ if (!error) sc->sc_input_fragment_length += n; splx(s); return (error); } /* * 現在の水位と space の和が水位上限を越える場合、 * audio_sleep() して水位が減るまで待つ。 */ while (cb->used + space >= cb->usedhigh) { DPRINTFN(2, ("audio_write: sleep used=%d lowat=%d " "hiwat=%d\n", cb->used, cb->usedlow, cb->usedhigh)); if (ioflag & IO_NDELAY) { splx(s); return (EWOULDBLOCK); } error = audio_sleep(&sc->sc_wchan, "aud_wr"); if (sc->sc_dying) error = EIO; if (error) { splx(s); return (error); } } used = cb->used; inp = cb->inp; cb->copying = 1; splx(s); /* * cc は現在の水位から上限まで、つまりリングバッファに * 書き込める最大値とする。 */ cc = cb->usedhigh - used; /* maximum to write */ /* * リングバッファのサイズより大きかったら律速する。 * XXX: リングバッファのサイズで律速していいんだっけ? */ /* cc may be greater than the size of the ring buffer */ if (cc > cb->end - cb->start) cc = cb->end - cb->start; /* * この時点で、cc はリングバッファに書き込める最大値? * samples をリングバッファに書き込めるハードウェアの * 最大サンプル数にする。 */ /* cc: # of bytes we can write to the ring buffer */ samples = cc * 8 / hw_bits_per_sample; #ifdef DIAGNOSTIC if (samples == 0) printf("audio_write: samples (cc/hw_bps) == 0\n"); #endif /* * samples をリングバッファに書き込めるユーザデータの * 最大サンプル数にする。 */ /* samples: # of samples we can write to the ring buffer */ samples = samples * params->sample_rate / params->hw_sample_rate; #ifdef DIAGNOSTIC if (samples == 0) printf("audio_write: samples (rate/hw_rate) == 0 " "usedhigh-used=%d cc/hw_bps=%d/%d " "rate/hw_rate=%ld/%ld space=%d\n", cb->usedhigh - cb->used, cc, hw_bits_per_sample / 8, params->sample_rate, params->hw_sample_rate, space); #endif /* * cc をリングバッファに書き込めるユーザデータの * 最大バイト数にする。 */ /* samples: # of samples in source data */ cc = samples * user_bits_per_sample / 8; /* cc: # of bytes in source data */ /* * 実際に処理するバイト数を求める。 * cc が処理できる最大バイト数なので、残りがそれ以上あれば * cc のまま。残り (input_remain) が cc より少なければ * input_remainを align して、そっちを使う。 */ if (input_remain < cc) /* and no more than we have */ cc = (input_remain * 8 / user_bits_per_sample) * user_bits_per_sample / 8; #ifdef DIAGNOSTIC if (cc == 0) printf("audio_write: cc == 0\n"); #endif /* * 後の sw_code でデータサイズが増えることを考慮しておく。 * データは sw_code * factor / factor_denom 倍になって * pconvbuffer に置かれるので、置けるかどうかチェックしている。 * 置けないようなら cc を置けるサイズに設定し直す。 */ if (cc * params->factor / params->factor_denom > sc->sc_pconvbuffer_size) { /* * cc = (pconv / factor / user_bps ) * user_bps */ cc = (sc->sc_pconvbuffer_size * params->factor_denom * 8 / params->factor / user_bits_per_sample) * user_bits_per_sample / 8; } #ifdef DIAGNOSTIC /* * This should never happen since the block size and and * block pointers are always nicely aligned. */ if (cc == 0) { printf("audio_write: cc == 0, swcode=%p, factor=%d " "remain=%d u_bps=%d hw_bps=%d\n", sc->sc_pparams.sw_code, sc->sc_pparams.factor, input_remain, user_bits_per_sample, hw_bits_per_sample); cb->copying = 0; return EINVAL; } #endif DPRINTFN(1, ("audio_write: uiomove cc=%d inp=%p, left=%lu\n", cc, inp, (unsigned long)uio->uio_resid)); /* * input_fragment(残りものバッファ) にあるデータを pconvbuffer * にコピーする。cc はその分引いておく。残りものバッファが空 * でも大丈夫。 */ memcpy(sc->sc_pconvbuffer, sc->sc_input_fragment, sc->sc_input_fragment_length); cc -= sc->sc_input_fragment_length; /* * その続き以降にユーザランドから残りのデータを読み込む。 * 指定した分だけ読めなかったらメッセージを表示する。 * 指定した分が読めないってことはあり得ないはず? */ n = uio->uio_resid; error = uiomove(sc->sc_pconvbuffer + sc->sc_input_fragment_length, cc, uio); if (cc != n - uio->uio_resid) { printf("audio_write: uiomove didn't move requested " "amount: requested=%d, actual=%ld\n", cc, (long)n - uio->uio_resid); } /* number of bytes actually moved */ /* * cc は実際に pconvbuffer に入っているデータのバイト数。 * input_fragment バッファは空になったので 0 にしておく。 */ cc = sc->sc_input_fragment_length + n - uio->uio_resid; sc->sc_input_fragment_length = 0; #ifdef AUDIO_DEBUG if (error) printf("audio_write:(1) uiomove failed %d; cc=%d " "inp=%p\n", error, cc, inp); #endif /* * Continue even if uiomove() failed because we may have * gotten a partial block. * uiomove() がこけても、データの一部分は受け取っている * かもしれないので、処理は続けるぜってことで。 */ /* * The format of data in sc_pconvbuffer is: * [sample_rate, encoding, precision, channels] * この時点で pconvbuffer の中のデータは * o ユーザランドから渡されたサンプリングレート * o ユーザランドから渡されたエンコード * o ユーザランドから渡された precision * o ユーザランドから渡されたチャンネル数 */ /* * sw_code があれば実行する。 * 再生時の sw_code に渡す cc は変換前のデータのバイト数 * らしい。pconvbuffer から cc バイトのデータを変換し * 結果は pconvbuffer からに上書きされる。 * 処理後に別途 factor / factor_denom を用いて計算する。 */ if (sc->sc_pparams.sw_code) { sc->sc_pparams.sw_code(sc->hw_hdl, sc->sc_pconvbuffer, cc); /* Adjust count after the expansion. */ cc = cc * sc->sc_pparams.factor / sc->sc_pparams.factor_denom; DPRINTFN(1, ("audio_write: expanded cc=%d\n", cc)); } /* * The format of data in sc_pconvbuffer is: * [sample_rate, hw_encoding, hw_precision, channels] * * この時点で pconvbuffer にあるデータは * o ユーザランドから渡されたサンプリングレート * o ハードウェア独自のエンコード * o ハードウェア独自の precision * o ユーザランドから渡されたチャンネル */ #if NAURATECONV > 0 /* * NAURATECONV が定義されているならこっちを実行。 * pconvbuffer から cc バイトのデータのサンプリング * レートとチャンネル数を変換し結果を inp 以降に格納 * する。出力されたデータのバイト数は cc に代入される。 */ cc = auconv_play(&sc->sc_pconv, params, inp, sc->sc_pconvbuffer, cc); #else /* * NAURATECONV が定義されていない場合こっちを実行。 * auconv_play() は結果的に pconvbuffer から inp * (リングバッファ cb の中) へのコピーを行なっている * ことになるので、それと同じことを memcpy で行なう。 */ n = cb->end - inp; if (cc <= n) { memcpy(inp, sc->sc_pconvbuffer, cc); } else { memcpy(inp, sc->sc_pconvbuffer, n); memcpy(cb->start, sc->sc_pconvbuffer + n, cc - n); } #endif /* !NAURATECONV */ /* * The format of data in inp is: * [hw_sample_rate, hw_encoding, hw_precision, hw_channels] * cc is the size of data actually written to inp. * この時点で inp からのデータは * o ハードウェア独自のサンプリングレート * o ハードウェア独自のエンコード * o ハードウェア独自の precision * o ハードウェア独自のチャンネル数 * cc は実際に inp に書いたデータサイズになっている。 */ /* * リングバッファ cb に書き戻すためのポインタを計算。 */ einp = cb->inp + cc; if (einp >= cb->end) einp -= cb->end - cb->start; /* not cb->bufsize */ s = splaudio(); /* * This is a very suboptimal way of keeping track of * silence in the buffer, but it is simple. */ sc->sc_sil_count = 0; cb->inp = einp; cb->used += cc; /* * If the interrupt routine wants the last block filled AND * the copy did not fill the last block completely it needs to * be padded. */ if (cb->needfill && (inp - cb->start) / cb->blksize == (einp - cb->start) / cb->blksize) { /* Figure out how many bytes to a block boundary. */ cc = cb->blksize - (einp - cb->start) % cb->blksize; DPRINTF(("audio_write: partial fill %d\n", cc)); } else cc = 0; cb->needfill = 0; cb->copying = 0; if (!sc->sc_pbus && !cb->pause) { saveerror = error; error = audiostartp(sc); if (saveerror != 0) { /* Report the first error that occurred. */ error = saveerror; } } splx(s); if (cc != 0) { DPRINTFN(1, ("audio_write: fill %d\n", cc)); audio_fill_silence(&sc->sc_pparams, einp, cc); } } return (error); } |