The USB Start of Frame packet is sent 1000 times a second from the host to a
USB device. This packet is used to synchronize audio data being sent to a USB
sound card. On my system the Start of Frame packet is only sent about 830 times
a second. This makes audio that is played thru the USB sound card sound kind of
slow.
Research into the problem indicates the rate QEMU is suppose to send the packet
isn't actually being done. How it is set up now the emulated sound hardware
just assumes the rate it is suppose to send data is what actually happens. This
isn't true. More variables need to be accounted for.
What I propose is code be made that actively monitors how many Start of Frame
packets are being sent in a given second. If the value goes down, then QEMU
should compensate and try to send more Start of Frame packets. If the value
goes too high, QEMU should throttle back on Start of Frame packet transmission.
This code is basically what I think should be put into place to help maintain a
consistent Start of Frame packet transmission rate. This code is easy to build
yourself and try out. What it does is makes two threads. One is a consumer and
the other is a producer thread. The consumer thread monitors the production of
the producer thread. The consumer thread can actively adjust the rate of the
producer thread to help it maintain a steady pace. You can test this program
out by having it run and seeing how its values change when you do CPU intensive
stuff. What I do is open a YouTube video and scroll in the window really fast
to see how the program adapts.
I would like to know if this looks like something that could do a good job at
maintaining a steady Start of Frame packet transmission rate. I'm hoping for
more suggestions and improvements I could make to this program before I try to
implement it in the hcd-ohci.c file.
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <memory.h>
#include <sched.h>
int counter, producer_sleep_value;
int goalPerSecond = 1000; // the number of cycles we want the producer
function to run at in a second
int range = 0;
int delta_sleep_value = 1;
#define MAX_NUM_VALUES 5
int sampleArray[MAX_NUM_VALUES];
int totalValue;
int totalNumberOfValues;
int checkInterval = 1000*30; // in microseconds sets how often we check on the
producer
float secondsPerCheckInterval;
int goalPerCheckInterval;
// Set some of the global variables
void init()
{
secondsPerCheckInterval = (float)checkInterval/(1000*1000);
goalPerCheckInterval = goalPerSecond*secondsPerCheckInterval;
producer_sleep_value = 1000;
}
// Obtain a sample count value and place it into the sampleArray
void inputSample(int new_value)
{
static int index;
totalNumberOfValues++;
totalValue += new_value;
sampleArray[index++] = new_value;
if (index >= MAX_NUM_VALUES) {
index = 0;
}
}
// Return the average value of all stored samples
float getAverageValue()
{
float total = 0;
for (int i = 0; i < MAX_NUM_VALUES; i++) {
total += sampleArray[i];
}
return total/MAX_NUM_VALUES;
}
// Return the average sample size over the last 30 seconds
float getTotalAverage()
{
int valuesPerTimeFrame;
float returnValue;
/* 1 value/checkInterval microseconds x 1,000,000 microseconds/second x 30
seconds = 1 * 1,000,000 * 30 / (checkInterval*1) =
30,000,000/checkInterval
*/
valuesPerTimeFrame = 30000000/checkInterval;
// only last 30 seconds
if (totalNumberOfValues >= valuesPerTimeFrame) {
totalNumberOfValues = 0;
totalValue = 0;
printf("30 SECOND RESET ************\n");
}
if (totalNumberOfValues == 0) {
returnValue = 0;
} else {
returnValue = (float)totalValue/totalNumberOfValues;
}
return returnValue;
}
// Figure out how close we are to the goal
float getGoalPercent()
{
return getTotalAverage() * ((float)1/goalPerCheckInterval) * 100;
}
// Tries to maintain a consistent amount of output from the producer
void *consumerFunc(void *dummy)
{
while(1) {
inputSample(counter);
printf("current = %3d, current average: %3.2f, percent goal:
%3.2f%c\n", counter, getAverageValue(), getGoalPercent(), '%');
// If the produced amount is too low
if (counter+range < goalPerCheckInterval) {
producer_sleep_value -= delta_sleep_value;
printf("Reducing sleep value: %d\n",
producer_sleep_value);
// If the produced amount is too high
} else if (counter-range > goalPerCheckInterval) {
producer_sleep_value += delta_sleep_value;
printf("Increasing sleep value: %d\n",
producer_sleep_value);
}
counter = 0;
usleep(checkInterval);
}
}
// Produces the output we need
void *producerFunc(void *dummy)
{
while(1) {
counter++;
if (usleep(producer_sleep_value) != 0)
printf("Problem detected with usleep!\n");
}
}
int main (int argc, const char * argv[]) {
init();
pthread_t consumerThread, producerThread;
pthread_create(&producerThread, NULL, producerFunc, NULL);
pthread_create(&consumerThread, NULL, consumerFunc, NULL);
pthread_join(producerThread, NULL);
pthread_join(consumerThread, NULL);
return 0;
}