| /****************************************************************************** |
| * * |
| * easycap_sound.c * |
| * * |
| * Audio driver for EasyCAP USB2.0 Video Capture Device DC60 * |
| * * |
| * * |
| ******************************************************************************/ |
| /* |
| * |
| * Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org> |
| * |
| * |
| * This is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * The software is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this software; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| /*****************************************************************************/ |
| |
| #include "easycap.h" |
| #include "easycap_debug.h" |
| #include "easycap_sound.h" |
| |
| /*****************************************************************************/ |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * ON COMPLETION OF AN AUDIO URB ITS DATA IS COPIED TO THE AUDIO BUFFERS |
| * PROVIDED peasycap->audio_idle IS ZER0. REGARDLESS OF THIS BEING TRUE, |
| * IT IS RESUBMITTED PROVIDED peasycap->audio_isoc_streaming IS NOT ZERO. |
| */ |
| /*---------------------------------------------------------------------------*/ |
| void |
| easysnd_complete(struct urb *purb) |
| { |
| static int mt; |
| struct easycap *peasycap; |
| struct data_buffer *paudio_buffer; |
| char errbuf[16]; |
| __u8 *p1, *p2; |
| __s16 s16; |
| int i, j, more, much, leap, rc; |
| |
| JOT(16, "\n"); |
| |
| if (NULL == purb) { |
| SAY("ERROR: purb is NULL\n"); |
| return; |
| } |
| peasycap = purb->context; |
| if (NULL == peasycap) { |
| SAY("ERROR: peasycap is NULL\n"); |
| return; |
| } |
| much = 0; |
| |
| |
| if (peasycap->audio_idle) { |
| JOT(16, "%i=audio_idle %i=audio_isoc_streaming\n", \ |
| peasycap->audio_idle, peasycap->audio_isoc_streaming); |
| if (peasycap->audio_isoc_streaming) { |
| rc = usb_submit_urb(purb, GFP_ATOMIC); |
| if (0 != rc) { |
| SAY("ERROR: while %i=audio_idle, " \ |
| "usb_submit_urb() failed with rc:\n", \ |
| peasycap->audio_idle); |
| switch (rc) { |
| case -ENOMEM: { |
| SAY("ENOMEM\n"); break; |
| } |
| case -ENODEV: { |
| SAY("ENODEV\n"); break; |
| } |
| case -ENXIO: { |
| SAY("ENXIO\n"); break; |
| } |
| case -EINVAL: { |
| SAY("EINVAL\n"); break; |
| } |
| case -EAGAIN: { |
| SAY("EAGAIN\n"); break; |
| } |
| case -EFBIG: { |
| SAY("EFBIG\n"); break; |
| } |
| case -EPIPE: { |
| SAY("EPIPE\n"); break; |
| } |
| case -EMSGSIZE: { |
| SAY("EMSGSIZE\n"); break; |
| } |
| default: { |
| SAY("0x%08X\n", rc); break; |
| } |
| } |
| } |
| } |
| return; |
| } |
| /*---------------------------------------------------------------------------*/ |
| if (purb->status) { |
| if (-ESHUTDOWN == purb->status) { |
| JOT(16, "immediate return because -ESHUTDOWN=purb->status\n"); |
| return; |
| } |
| SAY("ERROR: non-zero urb status:\n"); |
| switch (purb->status) { |
| case -EINPROGRESS: { |
| SAY("-EINPROGRESS\n"); break; |
| } |
| case -ENOSR: { |
| SAY("-ENOSR\n"); break; |
| } |
| case -EPIPE: { |
| SAY("-EPIPE\n"); break; |
| } |
| case -EOVERFLOW: { |
| SAY("-EOVERFLOW\n"); break; |
| } |
| case -EPROTO: { |
| SAY("-EPROTO\n"); break; |
| } |
| case -EILSEQ: { |
| SAY("-EILSEQ\n"); break; |
| } |
| case -ETIMEDOUT: { |
| SAY("-ETIMEDOUT\n"); break; |
| } |
| case -EMSGSIZE: { |
| SAY("-EMSGSIZE\n"); break; |
| } |
| case -EOPNOTSUPP: { |
| SAY("-EOPNOTSUPP\n"); break; |
| } |
| case -EPFNOSUPPORT: { |
| SAY("-EPFNOSUPPORT\n"); break; |
| } |
| case -EAFNOSUPPORT: { |
| SAY("-EAFNOSUPPORT\n"); break; |
| } |
| case -EADDRINUSE: { |
| SAY("-EADDRINUSE\n"); break; |
| } |
| case -EADDRNOTAVAIL: { |
| SAY("-EADDRNOTAVAIL\n"); break; |
| } |
| case -ENOBUFS: { |
| SAY("-ENOBUFS\n"); break; |
| } |
| case -EISCONN: { |
| SAY("-EISCONN\n"); break; |
| } |
| case -ENOTCONN: { |
| SAY("-ENOTCONN\n"); break; |
| } |
| case -ESHUTDOWN: { |
| SAY("-ESHUTDOWN\n"); break; |
| } |
| case -ENOENT: { |
| SAY("-ENOENT\n"); break; |
| } |
| case -ECONNRESET: { |
| SAY("-ECONNRESET\n"); break; |
| } |
| default: { |
| SAY("unknown error code 0x%08X\n", purb->status); break; |
| } |
| } |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * RESUBMIT THIS URB AFTER AN ERROR |
| * |
| * (THIS IS DUPLICATE CODE TO REDUCE INDENTATION OF THE NO-ERROR PATH) |
| */ |
| /*---------------------------------------------------------------------------*/ |
| if (peasycap->audio_isoc_streaming) { |
| rc = usb_submit_urb(purb, GFP_ATOMIC); |
| if (0 != rc) { |
| SAY("ERROR: while %i=audio_idle, usb_submit_urb() " |
| "failed with rc:\n", peasycap->audio_idle); |
| switch (rc) { |
| case -ENOMEM: { |
| SAY("ENOMEM\n"); break; |
| } |
| case -ENODEV: { |
| SAY("ENODEV\n"); break; |
| } |
| case -ENXIO: { |
| SAY("ENXIO\n"); break; |
| } |
| case -EINVAL: { |
| SAY("EINVAL\n"); break; |
| } |
| case -EAGAIN: { |
| SAY("EAGAIN\n"); break; |
| } |
| case -EFBIG: { |
| SAY("EFBIG\n"); break; |
| } |
| case -EPIPE: { |
| SAY("EPIPE\n"); break; |
| } |
| case -EMSGSIZE: { |
| SAY("EMSGSIZE\n"); break; |
| } |
| default: { |
| SAY("0x%08X\n", rc); break; |
| } |
| } |
| } |
| } |
| return; |
| } |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * PROCEED HERE WHEN NO ERROR |
| */ |
| /*---------------------------------------------------------------------------*/ |
| for (i = 0; i < purb->number_of_packets; i++) { |
| switch (purb->iso_frame_desc[i].status) { |
| case 0: { |
| strcpy(&errbuf[0], "OK"); break; |
| } |
| case -ENOENT: { |
| strcpy(&errbuf[0], "-ENOENT"); break; |
| } |
| case -EINPROGRESS: { |
| strcpy(&errbuf[0], "-EINPROGRESS"); break; |
| } |
| case -EPROTO: { |
| strcpy(&errbuf[0], "-EPROTO"); break; |
| } |
| case -EILSEQ: { |
| strcpy(&errbuf[0], "-EILSEQ"); break; |
| } |
| case -ETIME: { |
| strcpy(&errbuf[0], "-ETIME"); break; |
| } |
| case -ETIMEDOUT: { |
| strcpy(&errbuf[0], "-ETIMEDOUT"); break; |
| } |
| case -EPIPE: { |
| strcpy(&errbuf[0], "-EPIPE"); break; |
| } |
| case -ECOMM: { |
| strcpy(&errbuf[0], "-ECOMM"); break; |
| } |
| case -ENOSR: { |
| strcpy(&errbuf[0], "-ENOSR"); break; |
| } |
| case -EOVERFLOW: { |
| strcpy(&errbuf[0], "-EOVERFLOW"); break; |
| } |
| case -EREMOTEIO: { |
| strcpy(&errbuf[0], "-EREMOTEIO"); break; |
| } |
| case -ENODEV: { |
| strcpy(&errbuf[0], "-ENODEV"); break; |
| } |
| case -EXDEV: { |
| strcpy(&errbuf[0], "-EXDEV"); break; |
| } |
| case -EINVAL: { |
| strcpy(&errbuf[0], "-EINVAL"); break; |
| } |
| case -ECONNRESET: { |
| strcpy(&errbuf[0], "-ECONNRESET"); break; |
| } |
| case -ESHUTDOWN: { |
| strcpy(&errbuf[0], "-ESHUTDOWN"); break; |
| } |
| default: { |
| strcpy(&errbuf[0], "UNKNOWN"); break; |
| } |
| } |
| if ((!purb->iso_frame_desc[i].status) && 0) { |
| JOT(16, "frame[%2i]: %i=status{=%16s} " \ |
| "%5i=actual " \ |
| "%5i=length " \ |
| "%3i=offset\n", \ |
| i, purb->iso_frame_desc[i].status, &errbuf[0], |
| purb->iso_frame_desc[i].actual_length, |
| purb->iso_frame_desc[i].length, |
| purb->iso_frame_desc[i].offset); |
| } |
| if (!purb->iso_frame_desc[i].status) { |
| more = purb->iso_frame_desc[i].actual_length; |
| |
| #if defined(TESTTONE) |
| if (!more) |
| more = purb->iso_frame_desc[i].length; |
| #endif |
| |
| if (!more) |
| mt++; |
| else { |
| if (mt) { |
| JOT(16, "%4i empty audio urb frames\n", mt); |
| mt = 0; |
| } |
| |
| p1 = (__u8 *)(purb->transfer_buffer + \ |
| purb->iso_frame_desc[i].offset); |
| |
| leap = 0; |
| p1 += leap; |
| more -= leap; |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * COPY more BYTES FROM ISOC BUFFER TO AUDIO BUFFER, |
| * CONVERTING 8-BIT SAMPLES TO 16-BIT SIGNED LITTLE-ENDED SAMPLES IF NECESSARY |
| */ |
| /*---------------------------------------------------------------------------*/ |
| while (more) { |
| if (0 > more) { |
| SAY("easysnd_complete: MISTAKE: " \ |
| "more is negative\n"); |
| return; |
| } |
| if (audio_buffer_page_many <= \ |
| peasycap->audio_fill) { |
| SAY("ERROR: bad " \ |
| "peasycap->audio_fill\n"); |
| return; |
| } |
| |
| paudio_buffer = &peasycap->audio_buffer\ |
| [peasycap->audio_fill]; |
| if (PAGE_SIZE < (paudio_buffer->pto - \ |
| paudio_buffer->pgo)) { |
| SAY("ERROR: bad paudio_buffer->pto\n"); |
| return; |
| } |
| if (PAGE_SIZE == (paudio_buffer->pto - \ |
| paudio_buffer->pgo)) { |
| |
| #if defined(TESTTONE) |
| easysnd_testtone(peasycap, \ |
| peasycap->audio_fill); |
| #endif /*TESTTONE*/ |
| |
| paudio_buffer->pto = \ |
| paudio_buffer->pgo; |
| (peasycap->audio_fill)++; |
| if (audio_buffer_page_many <= \ |
| peasycap->audio_fill) |
| peasycap->audio_fill = 0; |
| |
| JOT(12, "bumped peasycap->" \ |
| "audio_fill to %i\n", \ |
| peasycap->audio_fill); |
| |
| paudio_buffer = &peasycap->\ |
| audio_buffer\ |
| [peasycap->audio_fill]; |
| paudio_buffer->pto = \ |
| paudio_buffer->pgo; |
| |
| if (!(peasycap->audio_fill % \ |
| audio_pages_per_fragment)) { |
| JOT(12, "wakeup call on wq_" \ |
| "audio, %i=frag reading %i" \ |
| "=fragment fill\n", \ |
| (peasycap->audio_read / \ |
| audio_pages_per_fragment), \ |
| (peasycap->audio_fill / \ |
| audio_pages_per_fragment)); |
| wake_up_interruptible\ |
| (&(peasycap->wq_audio)); |
| } |
| } |
| |
| much = PAGE_SIZE - (int)(paudio_buffer->pto -\ |
| paudio_buffer->pgo); |
| if (much % 2) |
| JOT(8, "MISTAKE? much is odd\n"); |
| |
| if (false == peasycap->microphone) { |
| if (much > more) |
| much = more; |
| |
| memcpy(paudio_buffer->pto, p1, much); |
| p1 += much; |
| more -= much; |
| } else { |
| if (much > (2 * more)) |
| much = 2 * more; |
| p2 = (__u8 *)paudio_buffer->pto; |
| |
| for (j = 0; j < (much / 2); j++) { |
| s16 = ((int) *p1) - 128; |
| *p2 = (0xFF00 & s16) >> 8; |
| *(p2 + 1) = (0x00FF & s16); |
| p1++; p2 += 2; |
| more--; |
| } |
| } |
| (paudio_buffer->pto) += much; |
| } |
| } |
| } else { |
| JOT(12, "discarding audio samples because " \ |
| "%i=purb->iso_frame_desc[i].status\n", \ |
| purb->iso_frame_desc[i].status); |
| } |
| } |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * RESUBMIT THIS URB AFTER NO ERROR |
| */ |
| /*---------------------------------------------------------------------------*/ |
| if (peasycap->audio_isoc_streaming) { |
| rc = usb_submit_urb(purb, GFP_ATOMIC); |
| if (0 != rc) { |
| SAY("ERROR: while %i=audio_idle, usb_submit_urb() failed " \ |
| "with rc:\n", peasycap->audio_idle); |
| switch (rc) { |
| case -ENOMEM: { |
| SAY("ENOMEM\n"); break; |
| } |
| case -ENODEV: { |
| SAY("ENODEV\n"); break; |
| } |
| case -ENXIO: { |
| SAY("ENXIO\n"); break; |
| } |
| case -EINVAL: { |
| SAY("EINVAL\n"); break; |
| } |
| case -EAGAIN: { |
| SAY("EAGAIN\n"); break; |
| } |
| case -EFBIG: { |
| SAY("EFBIG\n"); break; |
| } |
| case -EPIPE: { |
| SAY("EPIPE\n"); break; |
| } |
| case -EMSGSIZE: { |
| SAY("EMSGSIZE\n"); break; |
| } |
| default: { |
| SAY("0x%08X\n", rc); break; |
| } |
| } |
| } |
| } |
| return; |
| } |
| /*****************************************************************************/ |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * THE AUDIO URBS ARE SUBMITTED AT THIS EARLY STAGE SO THAT IT IS POSSIBLE TO |
| * STREAM FROM /dev/easysnd1 WITH SIMPLE PROGRAMS SUCH AS cat WHICH DO NOT |
| * HAVE AN IOCTL INTERFACE. THE VIDEO URBS, BY CONTRAST, MUST BE SUBMITTED |
| * MUCH LATER: SEE COMMENTS IN FILE easycap_main.c. |
| */ |
| /*---------------------------------------------------------------------------*/ |
| int |
| easysnd_open(struct inode *inode, struct file *file) |
| { |
| struct usb_interface *pusb_interface; |
| struct easycap *peasycap; |
| int subminor, rc; |
| |
| JOT(4, "begins.\n"); |
| |
| subminor = iminor(inode); |
| |
| pusb_interface = usb_find_interface(&easycap_usb_driver, subminor); |
| if (NULL == pusb_interface) { |
| SAY("ERROR: pusb_interface is NULL\n"); |
| SAY("ending unsuccessfully\n"); |
| return -1; |
| } |
| peasycap = usb_get_intfdata(pusb_interface); |
| if (NULL == peasycap) { |
| SAY("ERROR: peasycap is NULL\n"); |
| SAY("ending unsuccessfully\n"); |
| return -1; |
| } |
| |
| file->private_data = peasycap; |
| |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * INITIALIZATION. |
| */ |
| /*---------------------------------------------------------------------------*/ |
| JOT(4, "starting initialization\n"); |
| |
| if ((struct usb_device *)NULL == peasycap->pusb_device) { |
| SAY("ERROR: peasycap->pusb_device is NULL\n"); |
| return -EFAULT; |
| } else { |
| JOT(16, "0x%08lX=peasycap->pusb_device\n", \ |
| (long int)peasycap->pusb_device); |
| } |
| |
| rc = audio_setup(peasycap); |
| if (0 <= rc) |
| JOT(8, "audio_setup() returned %i\n", rc); |
| else |
| JOT(8, "easysnd open(): ERROR: audio_setup() returned %i\n", rc); |
| |
| if ((struct usb_device *)NULL == peasycap->pusb_device) { |
| SAY("ERROR: peasycap->pusb_device has become NULL\n"); |
| return -EFAULT; |
| } |
| rc = adjust_volume(peasycap, -8192); |
| if (0 != rc) { |
| SAY("ERROR: adjust_volume(default) returned %i\n", rc); |
| return -EFAULT; |
| } |
| /*---------------------------------------------------------------------------*/ |
| if ((struct usb_device *)NULL == peasycap->pusb_device) { |
| SAY("ERROR: peasycap->pusb_device has become NULL\n"); |
| return -EFAULT; |
| } |
| rc = usb_set_interface(peasycap->pusb_device, peasycap->audio_interface, \ |
| peasycap->audio_altsetting_on); |
| JOT(8, "usb_set_interface(.,%i,%i) returned %i\n", peasycap->audio_interface, \ |
| peasycap->audio_altsetting_on, rc); |
| |
| if ((struct usb_device *)NULL == peasycap->pusb_device) { |
| SAY("ERROR: peasycap->pusb_device has become NULL\n"); |
| return -EFAULT; |
| } |
| rc = wakeup_device(peasycap->pusb_device); |
| if (0 == rc) |
| JOT(8, "wakeup_device() returned %i\n", rc); |
| else |
| JOT(8, "easysnd open(): ERROR: wakeup_device() returned %i\n", rc); |
| |
| if ((struct usb_device *)NULL == peasycap->pusb_device) { |
| SAY("ERROR: peasycap->pusb_device has become NULL\n"); |
| return -EFAULT; |
| } |
| submit_audio_urbs(peasycap); |
| peasycap->audio_idle = 0; |
| |
| peasycap->timeval1.tv_sec = 0; |
| peasycap->timeval1.tv_usec = 0; |
| |
| JOT(4, "finished initialization\n"); |
| return 0; |
| } |
| /*****************************************************************************/ |
| int |
| easysnd_release(struct inode *inode, struct file *file) |
| { |
| struct easycap *peasycap; |
| |
| JOT(4, "begins\n"); |
| |
| peasycap = (struct easycap *)file->private_data; |
| if (NULL == peasycap) { |
| SAY("ERROR: peasycap is NULL.\n"); |
| return -EFAULT; |
| } |
| if (0 != kill_audio_urbs(peasycap)) { |
| SAY("ERROR: kill_audio_urbs() failed\n"); |
| return -EFAULT; |
| } |
| JOT(4, "ending successfully\n"); |
| return 0; |
| } |
| /*****************************************************************************/ |
| ssize_t |
| easysnd_read(struct file *file, char __user *puserspacebuffer, \ |
| size_t kount, loff_t *poff) |
| { |
| struct timeval timeval; |
| static struct timeval timeval1; |
| static long long int audio_bytes, above, below, mean; |
| struct signed_div_result sdr; |
| unsigned char *p0; |
| long int kount1, more, rc, l0, lm; |
| int fragment; |
| struct easycap *peasycap; |
| struct data_buffer *pdata_buffer; |
| size_t szret; |
| |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * DO A BLOCKING READ TO TRANSFER DATA TO USER SPACE. |
| * |
| ****************************************************************************** |
| ***** N.B. IF THIS FUNCTION RETURNS 0, NOTHING IS SEEN IN USER SPACE. ****** |
| ***** THIS CONDITION SIGNIFIES END-OF-FILE. ****** |
| ****************************************************************************** |
| */ |
| /*---------------------------------------------------------------------------*/ |
| |
| JOT(8, "===== easysnd_read(): kount=%i, *poff=%i\n", (int)kount, (int)(*poff)); |
| |
| peasycap = (struct easycap *)(file->private_data); |
| if (NULL == peasycap) { |
| SAY("ERROR in easysnd_read(): peasycap is NULL\n"); |
| return -EFAULT; |
| } |
| /*---------------------------------------------------------------------------*/ |
| if ((0 > peasycap->audio_read) || \ |
| (audio_buffer_page_many <= peasycap->audio_read)) { |
| SAY("ERROR: peasycap->audio_read out of range\n"); |
| return -EFAULT; |
| } |
| pdata_buffer = &peasycap->audio_buffer[peasycap->audio_read]; |
| if ((struct data_buffer *)NULL == pdata_buffer) { |
| SAY("ERROR: pdata_buffer is NULL\n"); |
| return -EFAULT; |
| } |
| JOT(12, "before wait, %i=frag read %i=frag fill\n", \ |
| (peasycap->audio_read / audio_pages_per_fragment), \ |
| (peasycap->audio_fill / audio_pages_per_fragment)); |
| fragment = (peasycap->audio_read / audio_pages_per_fragment); |
| while ((fragment == (peasycap->audio_fill / audio_pages_per_fragment)) || \ |
| (0 == (PAGE_SIZE - (pdata_buffer->pto - pdata_buffer->pgo)))) { |
| if (file->f_flags & O_NONBLOCK) { |
| JOT(16, "returning -EAGAIN as instructed\n"); |
| return -EAGAIN; |
| } |
| rc = wait_event_interruptible(peasycap->wq_audio, \ |
| (peasycap->audio_idle || peasycap->audio_eof || \ |
| ((fragment != (peasycap->audio_fill / \ |
| audio_pages_per_fragment)) && \ |
| (0 < (PAGE_SIZE - (pdata_buffer->pto - pdata_buffer->pgo)))))); |
| if (0 != rc) { |
| SAY("aborted by signal\n"); |
| return -ERESTARTSYS; |
| } |
| if (peasycap->audio_eof) { |
| JOT(8, "returning 0 because %i=audio_eof\n", \ |
| peasycap->audio_eof); |
| kill_audio_urbs(peasycap); |
| msleep(500); |
| return 0; |
| } |
| if (peasycap->audio_idle) { |
| JOT(16, "returning 0 because %i=audio_idle\n", \ |
| peasycap->audio_idle); |
| return 0; |
| } |
| if (!peasycap->audio_isoc_streaming) { |
| JOT(16, "returning 0 because audio urbs not streaming\n"); |
| return 0; |
| } |
| } |
| JOT(12, "after wait, %i=frag read %i=frag fill\n", \ |
| (peasycap->audio_read / audio_pages_per_fragment), \ |
| (peasycap->audio_fill / audio_pages_per_fragment)); |
| szret = (size_t)0; |
| while (fragment == (peasycap->audio_read / audio_pages_per_fragment)) { |
| if (NULL == pdata_buffer->pgo) { |
| SAY("ERROR: pdata_buffer->pgo is NULL\n"); |
| return -EFAULT; |
| } |
| if (NULL == pdata_buffer->pto) { |
| SAY("ERROR: pdata_buffer->pto is NULL\n"); |
| return -EFAULT; |
| } |
| kount1 = PAGE_SIZE - (pdata_buffer->pto - pdata_buffer->pgo); |
| if (0 > kount1) { |
| SAY("easysnd_read: MISTAKE: kount1 is negative\n"); |
| return -ERESTARTSYS; |
| } |
| if (!kount1) { |
| (peasycap->audio_read)++; |
| if (audio_buffer_page_many <= peasycap->audio_read) |
| peasycap->audio_read = 0; |
| JOT(12, "bumped peasycap->audio_read to %i\n", \ |
| peasycap->audio_read); |
| |
| if (fragment != (peasycap->audio_read / \ |
| audio_pages_per_fragment)) |
| break; |
| |
| if ((0 > peasycap->audio_read) || \ |
| (audio_buffer_page_many <= peasycap->audio_read)) { |
| SAY("ERROR: peasycap->audio_read out of range\n"); |
| return -EFAULT; |
| } |
| pdata_buffer = &peasycap->audio_buffer[peasycap->audio_read]; |
| if ((struct data_buffer *)NULL == pdata_buffer) { |
| SAY("ERROR: pdata_buffer is NULL\n"); |
| return -EFAULT; |
| } |
| if (NULL == pdata_buffer->pgo) { |
| SAY("ERROR: pdata_buffer->pgo is NULL\n"); |
| return -EFAULT; |
| } |
| if (NULL == pdata_buffer->pto) { |
| SAY("ERROR: pdata_buffer->pto is NULL\n"); |
| return -EFAULT; |
| } |
| kount1 = PAGE_SIZE - (pdata_buffer->pto - pdata_buffer->pgo); |
| } |
| JOT(12, "ready to send %li bytes\n", (long int) kount1); |
| JOT(12, "still to send %li bytes\n", (long int) kount); |
| more = kount1; |
| if (more > kount) |
| more = kount; |
| JOT(12, "agreed to send %li bytes from page %i\n", \ |
| more, peasycap->audio_read); |
| if (!more) |
| break; |
| |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * ACCUMULATE DYNAMIC-RANGE INFORMATION |
| */ |
| /*---------------------------------------------------------------------------*/ |
| p0 = (unsigned char *)pdata_buffer->pgo; l0 = 0; lm = more/2; |
| while (l0 < lm) { |
| SUMMER(p0, &peasycap->audio_sample, &peasycap->audio_niveau, \ |
| &peasycap->audio_square); l0++; p0 += 2; |
| } |
| /*---------------------------------------------------------------------------*/ |
| rc = copy_to_user(puserspacebuffer, pdata_buffer->pto, more); |
| if (0 != rc) { |
| SAY("ERROR: copy_to_user() returned %li\n", rc); |
| return -EFAULT; |
| } |
| *poff += (loff_t)more; |
| szret += (size_t)more; |
| pdata_buffer->pto += more; |
| puserspacebuffer += more; |
| kount -= (size_t)more; |
| } |
| JOT(12, "after read, %i=frag read %i=frag fill\n", \ |
| (peasycap->audio_read / audio_pages_per_fragment), \ |
| (peasycap->audio_fill / audio_pages_per_fragment)); |
| if (kount < 0) { |
| SAY("MISTAKE: %li=kount %li=szret\n", \ |
| (long int)kount, (long int)szret); |
| } |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * CALCULATE DYNAMIC RANGE FOR (VAPOURWARE) AUTOMATIC VOLUME CONTROL |
| */ |
| /*---------------------------------------------------------------------------*/ |
| if (peasycap->audio_sample) { |
| below = peasycap->audio_sample; |
| above = peasycap->audio_square; |
| sdr = signed_div(above, below); |
| above = sdr.quotient; |
| mean = peasycap->audio_niveau; |
| sdr = signed_div(mean, peasycap->audio_sample); |
| |
| JOT(12, "%8lli=mean %8lli=meansquare after %lli samples, =>\n", \ |
| sdr.quotient, above, peasycap->audio_sample); |
| |
| sdr = signed_div(above, 32768); |
| JOT(8, "audio dynamic range is roughly %lli\n", sdr.quotient); |
| } |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * UPDATE THE AUDIO CLOCK |
| */ |
| /*---------------------------------------------------------------------------*/ |
| do_gettimeofday(&timeval); |
| if (!peasycap->timeval1.tv_sec) { |
| audio_bytes = 0; |
| timeval1 = timeval; |
| |
| if (mutex_lock_interruptible(&(peasycap->mutex_timeval1))) |
| return -ERESTARTSYS; |
| peasycap->timeval1 = timeval1; |
| mutex_unlock(&(peasycap->mutex_timeval1)); |
| sdr.quotient = 192000; |
| } else { |
| audio_bytes += (long long int) szret; |
| below = ((long long int)(1000000)) * \ |
| ((long long int)(timeval.tv_sec - timeval1.tv_sec)) + \ |
| (long long int)(timeval.tv_usec - timeval1.tv_usec); |
| above = 1000000 * ((long long int) audio_bytes); |
| |
| if (below) |
| sdr = signed_div(above, below); |
| else |
| sdr.quotient = 192000; |
| } |
| JOT(8, "audio streaming at %lli bytes/second\n", sdr.quotient); |
| if (mutex_lock_interruptible(&(peasycap->mutex_timeval1))) |
| return -ERESTARTSYS; |
| peasycap->dnbydt = sdr.quotient; |
| mutex_unlock(&(peasycap->mutex_timeval1)); |
| |
| JOT(8, "returning %li\n", (long int)szret); |
| return szret; |
| } |
| /*****************************************************************************/ |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * SUBMIT ALL AUDIO URBS. |
| */ |
| /*---------------------------------------------------------------------------*/ |
| int |
| submit_audio_urbs(struct easycap *peasycap) |
| { |
| struct data_urb *pdata_urb; |
| struct urb *purb; |
| struct list_head *plist_head; |
| int j, isbad, m, rc; |
| int isbuf; |
| |
| if ((struct list_head *)NULL == peasycap->purb_audio_head) { |
| SAY("ERROR: peasycap->urb_audio_head uninitialized\n"); |
| return -EFAULT; |
| } |
| if ((struct usb_device *)NULL == peasycap->pusb_device) { |
| SAY("ERROR: peasycap->pusb_device is NULL\n"); |
| return -EFAULT; |
| } |
| if (!peasycap->audio_isoc_streaming) { |
| JOT(4, "initial submission of all audio urbs\n"); |
| rc = usb_set_interface(peasycap->pusb_device, |
| peasycap->audio_interface, \ |
| peasycap->audio_altsetting_on); |
| JOT(8, "usb_set_interface(.,%i,%i) returned %i\n", \ |
| peasycap->audio_interface, \ |
| peasycap->audio_altsetting_on, rc); |
| |
| isbad = 0; m = 0; |
| list_for_each(plist_head, (peasycap->purb_audio_head)) { |
| pdata_urb = list_entry(plist_head, struct data_urb, list_head); |
| if (NULL != pdata_urb) { |
| purb = pdata_urb->purb; |
| if (NULL != purb) { |
| isbuf = pdata_urb->isbuf; |
| |
| purb->interval = 1; |
| purb->dev = peasycap->pusb_device; |
| purb->pipe = \ |
| usb_rcvisocpipe(peasycap->pusb_device,\ |
| peasycap->audio_endpointnumber); |
| purb->transfer_flags = URB_ISO_ASAP; |
| purb->transfer_buffer = \ |
| peasycap->audio_isoc_buffer[isbuf].pgo; |
| purb->transfer_buffer_length = \ |
| peasycap->audio_isoc_buffer_size; |
| purb->complete = easysnd_complete; |
| purb->context = peasycap; |
| purb->start_frame = 0; |
| purb->number_of_packets = \ |
| peasycap->audio_isoc_framesperdesc; |
| for (j = 0; j < peasycap->\ |
| audio_isoc_framesperdesc; \ |
| j++) { |
| purb->iso_frame_desc[j].offset = j * \ |
| peasycap->\ |
| audio_isoc_maxframesize; |
| purb->iso_frame_desc[j].length = \ |
| peasycap->\ |
| audio_isoc_maxframesize; |
| } |
| |
| rc = usb_submit_urb(purb, GFP_KERNEL); |
| if (0 != rc) { |
| isbad++; |
| SAY("ERROR: usb_submit_urb() failed" \ |
| " for urb with rc:\n"); |
| switch (rc) { |
| case -ENOMEM: { |
| SAY("ENOMEM\n"); break; |
| } |
| case -ENODEV: { |
| SAY("ENODEV\n"); break; |
| } |
| case -ENXIO: { |
| SAY("ENXIO\n"); break; |
| } |
| case -EINVAL: { |
| SAY("EINVAL\n"); break; |
| } |
| case -EAGAIN: { |
| SAY("EAGAIN\n"); break; |
| } |
| case -EFBIG: { |
| SAY("EFBIG\n"); break; |
| } |
| case -EPIPE: { |
| SAY("EPIPE\n"); break; |
| } |
| case -EMSGSIZE: { |
| SAY("EMSGSIZE\n"); break; |
| } |
| default: { |
| SAY("unknown error code %i\n",\ |
| rc); break; |
| } |
| } |
| } else { |
| m++; |
| } |
| } else { |
| isbad++; |
| } |
| } else { |
| isbad++; |
| } |
| } |
| if (isbad) { |
| JOT(4, "attempting cleanup instead of submitting\n"); |
| list_for_each(plist_head, (peasycap->purb_audio_head)) { |
| pdata_urb = list_entry(plist_head, struct data_urb, \ |
| list_head); |
| if (NULL != pdata_urb) { |
| purb = pdata_urb->purb; |
| if (NULL != purb) |
| usb_kill_urb(purb); |
| } |
| } |
| peasycap->audio_isoc_streaming = 0; |
| } else { |
| peasycap->audio_isoc_streaming = 1; |
| JOT(4, "submitted %i audio urbs\n", m); |
| } |
| } else |
| JOT(4, "already streaming audio urbs\n"); |
| |
| return 0; |
| } |
| /*****************************************************************************/ |
| /*---------------------------------------------------------------------------*/ |
| /* |
| * KILL ALL AUDIO URBS. |
| */ |
| /*---------------------------------------------------------------------------*/ |
| int |
| kill_audio_urbs(struct easycap *peasycap) |
| { |
| int m; |
| struct list_head *plist_head; |
| struct data_urb *pdata_urb; |
| |
| if (peasycap->audio_isoc_streaming) { |
| if ((struct list_head *)NULL != peasycap->purb_audio_head) { |
| peasycap->audio_isoc_streaming = 0; |
| JOT(4, "killing audio urbs\n"); |
| m = 0; |
| list_for_each(plist_head, (peasycap->purb_audio_head)) { |
| pdata_urb = list_entry(plist_head, struct data_urb, |
| list_head); |
| if ((struct data_urb *)NULL != pdata_urb) { |
| if ((struct urb *)NULL != pdata_urb->purb) { |
| usb_kill_urb(pdata_urb->purb); |
| m++; |
| } |
| } |
| } |
| JOT(4, "%i audio urbs killed\n", m); |
| } else { |
| SAY("ERROR: peasycap->purb_audio_head is NULL\n"); |
| return -EFAULT; |
| } |
| } else { |
| JOT(8, "%i=audio_isoc_streaming, no audio urbs killed\n", \ |
| peasycap->audio_isoc_streaming); |
| } |
| return 0; |
| } |
| /*****************************************************************************/ |