The Broken Window Theory

本文探讨了破窗理论如何应用于软件开发中,强调修复坏设计、错误决策及劣质代码的重要性。通过对比整洁系统与混乱系统的差异,揭示忽视小问题如何导致整个项目的恶化。

The Broken Window Theory

In a previous entry, I touched on the broken window theory. You might be familiar with the Pragmatic Progammers' take on this:

Don't leave "broken windows" (bad designs, wrong decisions, or poor code) unrepaired. Fix each one as soon as it is discovered. If there is insufficient time to fix it properly, then board it up. Perhaps you can comment out the offending code, or display a "Not Implemented" message, or substitute dummy data instead. Take some action to prevent further damage and to show that you're on top of the situation.

We've seen clean, functional systems deteriorate pretty quickly once windows start breaking. There are other factors that can contribute to software rot, and we'll touch on some of them elsewhere, but neglect accelerates the rot faster than any other factor.

That's excellent advice for programmers, but it's not the complete story.

broken windows

The broken window theory is based on an Atlantic Monthly article published in 1982. It's worth reading the article to get a deeper understanding of the human factors driving the theory:

Second, at the community level, disorder and crime are usually inextricably linked, in a kind of developmental sequence. Social psychologists and police officers tend to agree that if a window in a building is broken and is left unrepaired, all the rest of the windows will soon be broken. This is as true in nice neighborhoods as in rundown ones. Window-breaking does not necessarily occur on a large scale because some areas are inhabited by determined window-breakers whereas others are populated by window-lovers; rather, one unrepaired broken window is a signal that no one cares, and so breaking more windows costs nothing. (It has always been fun.)

Philip Zimbardo, a Stanford psychologist, reported in 1969 on some experiments testing the broken-window theory. He arranged to have an automobile without license plates parked with its hood up on a street in the Bronx and a comparable automobile on a street in Palo Alto, California. The car in the Bronx was attacked by "vandals" within ten minutes of its "abandonment." The first to arrive were a family--father, mother, and young son--who removed the radiator and battery. Within twenty-four hours, virtually everything of value had been removed. Then random destruction began--windows were smashed, parts torn off, upholstery ripped. Children began to use the car as a playground. Most of the adult "vandals" were well-dressed, apparently clean-cut whites. The car in Palo Alto sat untouched for more than a week. Then Zimbardo smashed part of it with a sledgehammer. Soon, passersby were joining in. Within a few hours, the car had been turned upside down and utterly destroyed. Again, the "vandals" appeared to be primarily respectable whites.

Untended property becomes fair game for people out for fun or plunder and even for people who ordinarily would not dream of doing such things and who probably consider themselves law-abiding. Because of the nature of community life in the Bronx--its anonymity, the frequency with which cars are abandoned and things are stolen or broken, the past experience of "no one caring"--vandalism begins much more quickly than it does in staid Palo Alto, where people have come to believe that private possessions are cared for, and that mischievous behavior is costly. But vandalism can occur anywhere once communal barriers--the sense of mutual regard and the obligations of civility--are lowered by actions that seem to signal that "no one cares."

There's even an entire book on this subject. What's fascinating to me is that the mere perception of disorder-- even with seemingly irrelevant petty crimes like graffiti or minor vandalism -- precipitates a negative feedback loop that can result in total disorder:

We suggest that "untended" behavior also leads to the breakdown of community controls. A stable neighborhood of families who care for their homes, mind each other's children, and confidently frown on unwanted intruders can change, in a few years or even a few months, to an inhospitable and frightening jungle. A piece of property is abandoned, weeds grow up, a window is smashed. Adults stop scolding rowdy children; the children, emboldened, become more rowdy. Families move out, unattached adults move in. Teenagers gather in front of the corner store. The merchant asks them to move; they refuse. Fights occur. Litter accumulates. People start drinking in front of the grocery; in time, an inebriate slumps to the sidewalk and is allowed to sleep it off. Pedestrians are approached by panhandlers.

At this point it is not inevitable that serious crime will flourish or violent attacks on strangers will occur. But many residents will think that crime, especially violent crime, is on the rise, and they will modify their behavior accordingly. They will use the streets less often, and when on the streets will stay apart from their fellows, moving with averted eyes, silent lips, and hurried steps. "Don't get involved." For some residents, this growing atomization will matter little, because the neighborhood is not their "home" but "the place where they live." Their interests are elsewhere; they are cosmopolitans. But it will matter greatly to other people, whose lives derive meaning and satisfaction from local attachments rather than worldly involvement; for them, the neighborhood will cease to exist except for a few reliable friends whom they arrange to meet.

Programming is insanely detail oriented, and perhaps this is why: if you're not on top of the details, the perception is that things are out of control, and it's only a matter of time before your project spins out of control. Maybe we should be sweating the small stuff.


/* Copyright (C) 2002 Jean-Marc Valin File: speex_jitter.h Adaptive jitter buffer for Speex Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 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. - Neither the name of the Xiph.org Foundation 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 COPYRIGHT HOLDERS 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 FOUNDATION 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. */ /* TODO: - Add short-term estimate - Defensive programming + warn when last returned < last desired (begative buffering) + warn if update_delay not called between get() and tick() or is called twice in a row - Linked list structure for holding the packets instead of the current fixed-size array + return memory to a pool + allow pre-allocation of the pool + optional max number of elements - Statistics + drift + loss + late + jitter + buffering delay */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "arch.h" #include "speex/speex_jitter.h" #include "os_support.h" #ifndef NULL #define NULL 0 #endif #define SPEEX_JITTER_MAX_BUFFER_SIZE 200 /**< Maximum number of packets in jitter buffer */ #define TSUB(a,b) ((spx_int32_t)((a)-(b))) #define GT32(a,b) (((spx_int32_t)((a)-(b)))>0) #define GE32(a,b) (((spx_int32_t)((a)-(b)))>=0) #define LT32(a,b) (((spx_int32_t)((a)-(b)))<0) #define LE32(a,b) (((spx_int32_t)((a)-(b)))<=0) #define ROUND_DOWN(x, step) ((x)<0 ? ((x)-(step)+1)/(step)*(step) : (x)/(step)*(step)) #define MAX_TIMINGS 40 #define MAX_BUFFERS 3 #define TOP_DELAY 40 /** Buffer that keeps the time of arrival of the latest packets */ struct TimingBuffer { int filled; /**< Number of entries occupied in "timing" and "counts"*/ int curr_count; /**< Number of packet timings we got (including those we discarded) */ spx_int32_t timing[MAX_TIMINGS]; /**< Sorted list of all timings ("latest" packets first) */ spx_int16_t counts[MAX_TIMINGS]; /**< Order the packets were put in (will be used for short-term estimate) */ }; static void tb_init(struct TimingBuffer *tb) { tb->filled = 0; tb->curr_count = 0; } /* Add the timing of a new packet to the TimingBuffer */ static void tb_add(struct TimingBuffer *tb, spx_int16_t timing) { int pos; /* Discard packet that won't make it into the list because they're too early */ if (tb->filled >= MAX_TIMINGS && timing >= tb->timing[tb->filled-1]) { tb->curr_count++; return; } /* Find where the timing info goes in the sorted list */ pos = 0; /* FIXME: Do bisection instead of linear search */ while (pos<tb->filled && timing >= tb->timing[pos]) { pos++; } speex_assert(pos <= tb->filled && pos < MAX_TIMINGS); /* Shift everything so we can perform the insertion */ if (pos < tb->filled) { int move_size = tb->filled-pos; if (tb->filled == MAX_TIMINGS) move_size -= 1; SPEEX_MOVE(&tb->timing[pos+1], &tb->timing[pos], move_size); SPEEX_MOVE(&tb->counts[pos+1], &tb->counts[pos], move_size); } /* Insert */ tb->timing[pos] = timing; tb->counts[pos] = tb->curr_count; tb->curr_count++; if (tb->filled<MAX_TIMINGS) tb->filled++; } /** Jitter buffer structure */ struct JitterBuffer_ { spx_uint32_t pointer_timestamp; /**< Timestamp of what we will *get* next */ spx_uint32_t last_returned_timestamp; /**< Useful for getting the next packet with the same timestamp (for fragmented media) */ spx_uint32_t next_stop; /**< Estimated time the next get() will be called */ spx_int32_t buffered; /**< Amount of data we think is still buffered by the application (timestamp units)*/ spx_uint32_t buffer_size; JitterBufferPacket packets[SPEEX_JITTER_MAX_BUFFER_SIZE]; /**< Packets stored in the buffer */ spx_uint32_t arrival[SPEEX_JITTER_MAX_BUFFER_SIZE]; /**< Packet arrival time (0 means it was late, even though it's a valid timestamp) */ void (*destroy) (void *); /**< Callback for destroying a packet */ spx_int32_t delay_step; /**< Size of the steps when adjusting buffering (timestamp units) */ spx_int32_t concealment_size; /**< Size of the packet loss concealment "units" */ int reset_state; /**< True if state was just reset */ int buffer_margin; /**< How many frames we want to keep in the buffer (lower bound) */ int late_cutoff; /**< How late must a packet be for it not to be considered at all */ int interp_requested; /**< An interpolation is requested by speex_jitter_update_delay() */ int auto_adjust; /**< Whether to automatically adjust the delay at any time */ struct TimingBuffer _tb[MAX_BUFFERS]; /**< Don't use those directly */ struct TimingBuffer *timeBuffers[MAX_BUFFERS]; /**< Storing arrival time of latest frames so we can compute some stats */ int window_size; /**< Total window over which the late frames are counted */ int subwindow_size; /**< Sub-window size for faster computation */ int max_late_rate; /**< Absolute maximum amount of late packets tolerable (in percent) */ int latency_tradeoff; /**< Latency equivalent of losing one percent of packets */ int auto_tradeoff; /**< Latency equivalent of losing one percent of packets (automatic default) */ int lost_count; /**< Number of consecutive lost packets */ }; /** Based on available data, this computes the optimal delay for the jitter buffer. The optimised function is in timestamp units and is: cost = delay + late_factor*[number of frames that would be late if we used that delay] @param tb Array of buffers @param late_factor Equivalent cost of a late frame (in timestamp units) */ static spx_int16_t compute_opt_delay(JitterBuffer *jitter) { int i; spx_int16_t opt=0; spx_int32_t best_cost=0x7fffffff; int late = 0; int pos[MAX_BUFFERS]; int tot_count; float late_factor; int penalty_taken = 0; int best = 0; int worst = 0; spx_int32_t deltaT; struct TimingBuffer *tb; tb = jitter->_tb; /* Number of packet timings we have received (including those we didn't keep) */ tot_count = 0; for (i=0;i<MAX_BUFFERS;i++) tot_count += tb[i].curr_count; if (tot_count==0) return 0; /* Compute cost for one lost packet */ if (jitter->latency_tradeoff != 0) late_factor = jitter->latency_tradeoff * 100.0f / tot_count; else late_factor = jitter->auto_tradeoff * jitter->window_size/tot_count; /*fprintf(stderr, "late_factor = %f\n", late_factor);*/ for (i=0;i<MAX_BUFFERS;i++) pos[i] = 0; /* Pick the TOP_DELAY "latest" packets (doesn't need to actually be late for the current settings) */ for (i=0;i<TOP_DELAY;i++) { int j; int next=-1; int latest = 32767; /* Pick latest among all sub-windows */ for (j=0;j<MAX_BUFFERS;j++) { if (pos[j] < tb[j].filled && tb[j].timing[pos[j]] < latest) { next = j; latest = tb[j].timing[pos[j]]; } } if (next != -1) { spx_int32_t cost; if (i==0) worst = latest; best = latest; latest = ROUND_DOWN(latest, jitter->delay_step); pos[next]++; /* Actual cost function that tells us how bad using this delay would be */ cost = -latest + late_factor*late; /*fprintf(stderr, "cost %d = %d + %f * %d\n", cost, -latest, late_factor, late);*/ if (cost < best_cost) { best_cost = cost; opt = latest; } } else { break; } /* For the next timing we will consider, there will be one more late packet to count */ late++; /* Two-frame penalty if we're going to increase the amount of late frames (hysteresis) */ if (latest >= 0 && !penalty_taken) { penalty_taken = 1; late+=4; } } deltaT = best-worst; /* This is a default "automatic latency tradeoff" when none is provided */ jitter->auto_tradeoff = 1 + deltaT/TOP_DELAY; /*fprintf(stderr, "auto_tradeoff = %d (%d %d %d)\n", jitter->auto_tradeoff, best, worst, i);*/ /* FIXME: Compute a short-term estimate too and combine with the long-term one */ /* Prevents reducing the buffer size when we haven't really had much data */ if (tot_count < TOP_DELAY && opt > 0) return 0; return opt; } /** Initialise jitter buffer */ EXPORT JitterBuffer *jitter_buffer_init(int step_size) { JitterBuffer *jitter = (JitterBuffer*)speex_alloc(sizeof(JitterBuffer)); if (jitter) { int i; spx_int32_t tmp; for (i=0;i<SPEEX_JITTER_MAX_BUFFER_SIZE;i++) jitter->packets[i].data=NULL; jitter->delay_step = step_size; jitter->concealment_size = step_size; /*FIXME: Should this be 0 or 1?*/ jitter->buffer_margin = 0; jitter->late_cutoff = 50; jitter->destroy = NULL; jitter->latency_tradeoff = 0; jitter->auto_adjust = 1; jitter->buffer_size = SPEEX_JITTER_MAX_BUFFER_SIZE; tmp = 4; jitter_buffer_ctl(jitter, JITTER_BUFFER_SET_MAX_LATE_RATE, &tmp); jitter_buffer_reset(jitter); } return jitter; } /** Reset jitter buffer */ EXPORT void jitter_buffer_reset(JitterBuffer *jitter) { int i; for (i=0;i<SPEEX_JITTER_MAX_BUFFER_SIZE;i++) { if (jitter->packets[i].data) { if (jitter->destroy) jitter->destroy(jitter->packets[i].data); else speex_free(jitter->packets[i].data); jitter->packets[i].data = NULL; } } /* Timestamp is actually undefined at this point */ jitter->pointer_timestamp = 0; jitter->next_stop = 0; jitter->reset_state = 1; jitter->lost_count = 0; jitter->buffered = 0; jitter->auto_tradeoff = 32000; for (i=0;i<MAX_BUFFERS;i++) { tb_init(&jitter->_tb[i]); jitter->timeBuffers[i] = &jitter->_tb[i]; } /*fprintf (stderr, "reset\n");*/ } /** Destroy jitter buffer */ EXPORT void jitter_buffer_destroy(JitterBuffer *jitter) { jitter_buffer_reset(jitter); speex_free(jitter); } /** Take the following timing into consideration for future calculations */ static void update_timings(JitterBuffer *jitter, spx_int32_t timing) { if (timing < -32767) timing = -32767; if (timing > 32767) timing = 32767; /* If the current sub-window is full, perform a rotation and discard oldest sub-widow */ if (jitter->timeBuffers[0]->curr_count >= jitter->subwindow_size) { int i; /*fprintf(stderr, "Rotate buffer\n");*/ struct TimingBuffer *tmp = jitter->timeBuffers[MAX_BUFFERS-1]; for (i=MAX_BUFFERS-1;i>=1;i--) jitter->timeBuffers[i] = jitter->timeBuffers[i-1]; jitter->timeBuffers[0] = tmp; tb_init(jitter->timeBuffers[0]); } tb_add(jitter->timeBuffers[0], timing); } /** Compensate all timings when we do an adjustment of the buffering */ static void shift_timings(JitterBuffer *jitter, spx_int16_t amount) { int i, j; for (i=0;i<MAX_BUFFERS;i++) { for (j=0;j<jitter->timeBuffers[i]->filled;j++) jitter->timeBuffers[i]->timing[j] += amount; } } /** Put one packet into the jitter buffer */ EXPORT void jitter_buffer_put(JitterBuffer *jitter, const JitterBufferPacket *packet) { spx_uint32_t i,j; int late; /*fprintf (stderr, "put packet %d %d\n", timestamp, span);*/ /* Cleanup buffer (remove old packets that weren't played) */ if (!jitter->reset_state) { for (i=0;i<jitter->buffer_size;i++) { /* Make sure we don't discard a "just-late" packet in case we want to play it next (if we interpolate). */ if (jitter->packets[i].data && LE32(jitter->packets[i].timestamp + jitter->packets[i].span, jitter->pointer_timestamp)) { /*fprintf (stderr, "cleaned (not played)\n");*/ if (jitter->destroy) jitter->destroy(jitter->packets[i].data); else speex_free(jitter->packets[i].data); jitter->packets[i].data = NULL; } } } /*fprintf(stderr, "arrival: %d %d %d\n", packet->timestamp, jitter->next_stop, jitter->pointer_timestamp);*/ /* Check if packet is late (could still be useful though) */ if (!jitter->reset_state && LT32(packet->timestamp, jitter->next_stop)) { update_timings(jitter, ((spx_int32_t)packet->timestamp) - ((spx_int32_t)jitter->next_stop) - jitter->buffer_margin); late = 1; } else { late = 0; } /* For some reason, the consumer has failed the last 20 fetches. Make sure this packet is * used to resync. */ if (jitter->lost_count>20) { jitter_buffer_reset(jitter); } /* Only insert the packet if it's not hopelessly late (i.e. totally useless) */ if (jitter->reset_state || GE32(packet->timestamp+packet->span+jitter->delay_step, jitter->pointer_timestamp)) { /*Find an empty slot in the buffer*/ for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data==NULL) break; } /*No place left in the buffer, need to make room for it by discarding the oldest packet */ if (i==jitter->buffer_size) { int earliest=jitter->packets[0].timestamp; i=0; for (j=1;j<jitter->buffer_size;j++) { if (!jitter->packets[i].data || LT32(jitter->packets[j].timestamp,earliest)) { earliest = jitter->packets[j].timestamp; i=j; } } if (jitter->destroy) jitter->destroy(jitter->packets[i].data); else speex_free(jitter->packets[i].data); jitter->packets[i].data=NULL; /*fprintf (stderr, "Buffer is full, discarding earliest frame %d (currently at %d)\n", timestamp, jitter->pointer_timestamp);*/ } /* Copy packet in buffer */ if (jitter->destroy) { jitter->packets[i].data = packet->data; } else { jitter->packets[i].data=(char*)speex_alloc(packet->len); for (j=0;j<packet->len;j++) jitter->packets[i].data[j]=packet->data[j]; } jitter->packets[i].timestamp=packet->timestamp; jitter->packets[i].span=packet->span; jitter->packets[i].len=packet->len; jitter->packets[i].sequence=packet->sequence; jitter->packets[i].user_data=packet->user_data; if (jitter->reset_state || late) jitter->arrival[i] = 0; else jitter->arrival[i] = jitter->next_stop;/*预计下一个被调用时间*/ } else { /* The original version of libspeex-dsp leaks packets when we * get here, since the application has no way of knowing whether * a packet was actually queued or not: as such, when this * happens, we destroy the packet that was passed ourselves */ if (jitter->destroy) jitter->destroy(packet->data); } } /** Get one packet from the jitter buffer */ EXPORT int jitter_buffer_get(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t desired_span, spx_int32_t *start_offset) { spx_uint32_t i; unsigned int j; spx_int16_t opt; if (start_offset != NULL) *start_offset = 0; /* Syncing on the first call */ if (jitter->reset_state) { int found = 0; /* Find the oldest packet */ spx_uint32_t oldest=0; for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && (!found || LT32(jitter->packets[i].timestamp,oldest))) { oldest = jitter->packets[i].timestamp; found = 1; } } if (found) { jitter->reset_state=0; jitter->pointer_timestamp = oldest; jitter->next_stop = oldest; } else { packet->timestamp = 0; packet->span = jitter->interp_requested; return JITTER_BUFFER_MISSING; } } jitter->last_returned_timestamp = jitter->pointer_timestamp; /* 如果存在插值请求 */ if (jitter->interp_requested != 0) { packet->timestamp = jitter->pointer_timestamp; packet->span = jitter->interp_requested; /* Increment the pointer because it got decremented in the delay update */ jitter->pointer_timestamp += jitter->interp_requested; packet->len = 0; /* fprintf (stderr, "Deferred interpolate\n"); */ jitter->interp_requested = 0; jitter->buffered = packet->span - desired_span; return JITTER_BUFFER_INSERTION; } /* Searching for the packet that fits best */ /* Search the buffer for a packet with the right timestamp and spanning the whole current chunk 依次尝试寻找时间戳匹配且跨度足够的数据包、时间戳更早但跨度足够的数据包、时间戳更早且部分覆盖当前块的数据包,以及最早且跨度最大的数据包。 若找到合适的数据包,更新相关时间信息,复制数据包内容,更新指针时间戳, */ for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && jitter->packets[i].timestamp==jitter->pointer_timestamp && GE32(jitter->packets[i].timestamp+jitter->packets[i].span, jitter->pointer_timestamp+desired_span)) break; } /* If no match, try for an "older" packet that still spans (fully) the current chunk */ if (i==jitter->buffer_size) { for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && LE32(jitter->packets[i].timestamp, jitter->pointer_timestamp) && GE32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp+desired_span)) break; } } /* If still no match, try for an "older" packet that spans part of the current chunk */ if (i==jitter->buffer_size) { for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && LE32(jitter->packets[i].timestamp, jitter->pointer_timestamp) && GT32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp)) break; } } /* If still no match, try for earliest packet possible */ if (i==jitter->buffer_size) { int found = 0; spx_uint32_t best_time=0; int best_span=0; int besti=0; for (i=0;i<jitter->buffer_size;i++) { /* check if packet starts within current chunk */ if (jitter->packets[i].data && LT32(jitter->packets[i].timestamp,jitter->pointer_timestamp+desired_span) && GE32(jitter->packets[i].timestamp,jitter->pointer_timestamp)) { if (!found || LT32(jitter->packets[i].timestamp,best_time) || (jitter->packets[i].timestamp==best_time && GT32(jitter->packets[i].span,best_span))) { best_time = jitter->packets[i].timestamp; best_span = jitter->packets[i].span; besti = i; found = 1; } } } if (found) { i=besti; /*fprintf (stderr, "incomplete: %d %d %d %d\n", jitter->packets[i].timestamp, jitter->pointer_timestamp, chunk_size, jitter->packets[i].span);*/ } } /* If we find something */ if (i!=jitter->buffer_size) { spx_int32_t offset; /* We (obviously) haven't lost this packet */ jitter->lost_count = 0; /* In this case, 0 isn't as a valid timestamp */ if (jitter->arrival[i] != 0) { update_timings(jitter, ((spx_int32_t)jitter->packets[i].timestamp) - ((spx_int32_t)jitter->arrival[i]) - jitter->buffer_margin); } /* Copy packet */ if (jitter->destroy) { packet->data = jitter->packets[i].data; packet->len = jitter->packets[i].len; } else { if (jitter->packets[i].len > packet->len) { speex_warning_int("jitter_buffer_get(): packet too large to fit. Size is", jitter->packets[i].len); } else { packet->len = jitter->packets[i].len; } for (j=0;j<packet->len;j++) packet->data[j] = jitter->packets[i].data[j]; /* Remove packet */ speex_free(jitter->packets[i].data); } jitter->packets[i].data = NULL; /* Set timestamp and span (if requested) */ offset = (spx_int32_t)jitter->packets[i].timestamp-(spx_int32_t)jitter->pointer_timestamp; if (start_offset != NULL) *start_offset = offset; else if (offset != 0) speex_warning_int("jitter_buffer_get() discarding non-zero start_offset", offset); packet->timestamp = jitter->packets[i].timestamp; jitter->last_returned_timestamp = packet->timestamp; packet->span = jitter->packets[i].span; packet->sequence = jitter->packets[i].sequence; packet->user_data = jitter->packets[i].user_data; /* Point to the end of the current packet */ jitter->pointer_timestamp = jitter->packets[i].timestamp+jitter->packets[i].span; jitter->buffered = packet->span - desired_span; if (start_offset != NULL) jitter->buffered += *start_offset; return JITTER_BUFFER_OK; } /* If we haven't found anything worth returning */ /*fprintf (stderr, "not found\n");*/ jitter->lost_count++; /*fprintf (stderr, "m");*/ /*fprintf (stderr, "lost_count = %d\n", jitter->lost_count);*/ opt = compute_opt_delay(jitter); /* Should we force an increase in the buffer or just do normal interpolation? */ if (opt < 0) { /* Need to increase buffering */ /* Shift histogram to compensate */ shift_timings(jitter, -opt); packet->timestamp = jitter->pointer_timestamp; packet->span = -opt; /* Don't move the pointer_timestamp forward */ packet->len = 0; jitter->buffered = packet->span - desired_span; return JITTER_BUFFER_INSERTION; /*jitter->pointer_timestamp -= jitter->delay_step;*/ /*fprintf (stderr, "Forced to interpolate\n");*/ } else { /* Normal packet loss */ packet->timestamp = jitter->pointer_timestamp; desired_span = ROUND_DOWN(desired_span, jitter->concealment_size); packet->span = desired_span; jitter->pointer_timestamp += desired_span; packet->len = 0; jitter->buffered = packet->span - desired_span; return JITTER_BUFFER_MISSING; /*fprintf (stderr, "Normal loss\n");*/ } } EXPORT int jitter_buffer_get_another(JitterBuffer *jitter, JitterBufferPacket *packet) { spx_uint32_t i, j; for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && jitter->packets[i].timestamp==jitter->last_returned_timestamp) break; } if (i!=jitter->buffer_size) { /* Copy packet */ packet->len = jitter->packets[i].len; if (jitter->destroy) { packet->data = jitter->packets[i].data; } else { for (j=0;j<packet->len;j++) packet->data[j] = jitter->packets[i].data[j]; /* Remove packet */ speex_free(jitter->packets[i].data); } jitter->packets[i].data = NULL; packet->timestamp = jitter->packets[i].timestamp; packet->span = jitter->packets[i].span; packet->sequence = jitter->packets[i].sequence; packet->user_data = jitter->packets[i].user_data; return JITTER_BUFFER_OK; } else { packet->data = NULL; packet->len = 0; packet->span = 0; return JITTER_BUFFER_MISSING; } } /* Let the jitter buffer know it's the right time to adjust the buffering delay to the network conditions */ static int _jitter_buffer_update_delay(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t *start_offset) { spx_int16_t opt = compute_opt_delay(jitter); /*fprintf(stderr, "opt adjustment is %d ", opt);*/ if (opt < 0) { shift_timings(jitter, -opt); jitter->pointer_timestamp += opt; jitter->interp_requested = -opt; /*fprintf (stderr, "Decision to interpolate %d samples\n", -opt);*/ } else if (opt > 0) { shift_timings(jitter, -opt); jitter->pointer_timestamp += opt; /*fprintf (stderr, "Decision to drop %d samples\n", opt);*/ } return opt; } /* Let the jitter buffer know it's the right time to adjust the buffering delay to the network conditions */ EXPORT int jitter_buffer_update_delay(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t *start_offset) { /* If the programmer calls jitter_buffer_update_delay() directly, automatically disable auto-adjustment */ jitter->auto_adjust = 0; return _jitter_buffer_update_delay(jitter, packet, start_offset); } /** Get pointer timestamp of jitter buffer */ EXPORT int jitter_buffer_get_pointer_timestamp(JitterBuffer *jitter) { return jitter->pointer_timestamp; } EXPORT void jitter_buffer_tick(JitterBuffer *jitter) { /* Automatically-adjust the buffering delay if requested */ if (jitter->auto_adjust) _jitter_buffer_update_delay(jitter, NULL, NULL); if (jitter->buffered >= 0) { jitter->next_stop = jitter->pointer_timestamp - jitter->buffered; } else { jitter->next_stop = jitter->pointer_timestamp; speex_warning_int("jitter buffer sees negative buffering, your code might be broken. Value is ", jitter->buffered); } jitter->buffered = 0; } EXPORT void jitter_buffer_remaining_span(JitterBuffer *jitter, spx_uint32_t rem) { /* Automatically-adjust the buffering delay if requested */ if (jitter->auto_adjust) _jitter_buffer_update_delay(jitter, NULL, NULL); if (jitter->buffered < 0) speex_warning_int("jitter buffer sees negative buffering, your code might be broken. Value is ", jitter->buffered); jitter->next_stop = jitter->pointer_timestamp - rem; } /* Used like the ioctl function to control the jitter buffer parameters */ EXPORT int jitter_buffer_ctl(JitterBuffer *jitter, int request, void *ptr) { int count; spx_uint32_t i; spx_int32_t buffer_size; switch(request) { case JITTER_BUFFER_SET_MARGIN: jitter->buffer_margin = *(spx_int32_t*)ptr; break; case JITTER_BUFFER_GET_MARGIN: *(spx_int32_t*)ptr = jitter->buffer_margin; break; case JITTER_BUFFER_GET_AVALIABLE_COUNT: count = 0; for (i=0;i<jitter->buffer_size;i++) { if (jitter->packets[i].data && LE32(jitter->pointer_timestamp, jitter->packets[i].timestamp)) { count++; } } *(spx_int32_t*)ptr = count; break; case JITTER_BUFFER_SET_DESTROY_CALLBACK: jitter->destroy = (void (*) (void *))ptr; break; case JITTER_BUFFER_GET_DESTROY_CALLBACK: *(void (**) (void *))ptr = jitter->destroy; break; case JITTER_BUFFER_SET_DELAY_STEP: jitter->delay_step = *(spx_int32_t*)ptr; break; case JITTER_BUFFER_GET_DELAY_STEP: *(spx_int32_t*)ptr = jitter->delay_step; break; case JITTER_BUFFER_SET_CONCEALMENT_SIZE: jitter->concealment_size = *(spx_int32_t*)ptr; break; case JITTER_BUFFER_GET_CONCEALMENT_SIZE: *(spx_int32_t*)ptr = jitter->concealment_size; break; case JITTER_BUFFER_SET_MAX_LATE_RATE: jitter->max_late_rate = *(spx_int32_t*)ptr; jitter->window_size = 100*TOP_DELAY/jitter->max_late_rate; jitter->subwindow_size = jitter->window_size/MAX_BUFFERS; break; case JITTER_BUFFER_GET_MAX_LATE_RATE: *(spx_int32_t*)ptr = jitter->max_late_rate; break; case JITTER_BUFFER_SET_LATE_COST: jitter->latency_tradeoff = *(spx_int32_t*)ptr; break; case JITTER_BUFFER_GET_LATE_COST: *(spx_int32_t*)ptr = jitter->latency_tradeoff; break; case JITTER_BUFFER_SET_LIMIT: buffer_size = *(spx_int32_t*)ptr; jitter->buffer_size = (buffer_size > 1 && buffer_size <= SPEEX_JITTER_MAX_BUFFER_SIZE) ? buffer_size : SPEEX_JITTER_MAX_BUFFER_SIZE; jitter_buffer_reset(jitter); break; default: speex_warning_int("Unknown jitter_buffer_ctl request: ", request); return -1; } return 0; } 全面解读这个代码
09-15
内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MT型PLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了型PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编写梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选型,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选型、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计或工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计或调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值